From c1bf12be62c6a7fa32930bd2b4e920f36fbbc7a3 Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Mon, 31 Aug 2026 11:20:15 +0530 Subject: [PATCH 1/8] implement Attendee bot webhooks and lifecycle endpoints --- docs/architecture/AI_PROCESSING.md | 5 ++- docs/architecture/DB_DESIGN.md | 26 ++++++++--- services/ai-core-services/.env.example | 5 +++ .../src/api/v1/meeting_bot.py | 34 +++++++++++++- services/ai-core-services/src/core/config.py | 9 ++++ .../src/meeting_bot/attendee.py | 44 ++++++++++++++++++- .../src/meeting_bot/client.py | 9 ++++ .../src/meeting_bot/schemas.py | 11 +++++ 8 files changed, 131 insertions(+), 12 deletions(-) diff --git a/docs/architecture/AI_PROCESSING.md b/docs/architecture/AI_PROCESSING.md index 3fc1f8e..4f00728 100644 --- a/docs/architecture/AI_PROCESSING.md +++ b/docs/architecture/AI_PROCESSING.md @@ -737,6 +737,7 @@ SCORING RULES: DECISION: - "NEXT_QUESTION" if score >= 6 AND coverage_percent >= 50 (candidate understood it well enough for screening). - "ASK_FOLLOW_UP" if score < 6 OR coverage_percent < 50 (answer was too shallow or missed key concepts). +- "REPEAT_QUESTION" if the candidate asked you to repeat the question, or if their response was completely unrelated to the interview (e.g. "I can't hear you", "Hold on a second"). Return STRICT JSON only. No markdown: { @@ -745,9 +746,9 @@ Return STRICT JSON only. No markdown: "keywords_found": ["..."], "keywords_missing": ["..."], "is_sufficient": , - "decision": "NEXT_QUESTION | ASK_FOLLOW_UP", + "decision": "NEXT_QUESTION | ASK_FOLLOW_UP | REPEAT_QUESTION", "feedback": "2-3 sentences: what was good, what was missing, pass/fail on this topic for screening", - "suggested_follow_up": "If decision is ASK_FOLLOW_UP and this is NOT a follow-up evaluation itself, write a specific, conversational follow-up question here to probe what they missed based on the missing keywords. Otherwise omit this field entirely." + "suggested_follow_up": "If decision is ASK_FOLLOW_UP and this is NOT a follow-up evaluation itself, write a specific, conversational follow-up question here to probe what they missed based on the missing keywords. If REPEAT_QUESTION, omit this field." } ``` diff --git a/docs/architecture/DB_DESIGN.md b/docs/architecture/DB_DESIGN.md index 1732008..337667f 100644 --- a/docs/architecture/DB_DESIGN.md +++ b/docs/architecture/DB_DESIGN.md @@ -495,20 +495,32 @@ This dynamic list of questions is tailored to the candidate and generated by the ```json [ { + "interaction_type": "greeting", + "bot_speech": "Hello! I'm your AI interviewer today. Let's begin with a few technical questions.", + "candidate_answer": "Hi, sounds good!" + }, + { + "interaction_type": "question", "question_id": 1, - "question": "Can you explain the difference between a HashMap and a ConcurrentHashMap?", - "candidate_answer": "So, a HashMap isn't thread-safe, while a ConcurrentHashMap is designed for multi-threaded environments.", + "bot_speech": "Can you explain the difference between a HashMap and a ConcurrentHashMap?", + "candidate_answer": "Can you repeat that please? I had a connection drop.", "follow_ups": [ { - "follow_up_question": "You mentioned that ConcurrentHashMap handles synchronization for you; can you explain how it does that differently?", - "follow_up_answer": "Right, so instead of locking the entire map, ConcurrentHashMap uses a technique called lock stripping." + "interaction_type": "clarification", + "bot_speech": "Sure, no problem. Can you explain the difference between a HashMap and a ConcurrentHashMap?", + "candidate_answer": "Ah, okay. So, a HashMap isn't thread-safe, while a ConcurrentHashMap is designed for multi-threaded environments." + }, + { + "interaction_type": "discussion", + "bot_speech": "You mentioned that ConcurrentHashMap handles synchronization for you; can you explain how it does that differently?", + "candidate_answer": "Right, so instead of locking the entire map, ConcurrentHashMap uses a technique called lock stripping." } ] }, { - "question_id": 2, - "question": "When would you use a JOIN versus a Subquery to retrieve data?", - "candidate_answer": "I generally use joins when I need to combine columns from multiple tables, while subqueries are better for filtering." + "interaction_type": "closing", + "bot_speech": "Thank you for your time today. Our HR team will be in touch shortly.", + "candidate_answer": "Thank you, bye!" } ] ``` diff --git a/services/ai-core-services/.env.example b/services/ai-core-services/.env.example index 7283053..222fe67 100644 --- a/services/ai-core-services/.env.example +++ b/services/ai-core-services/.env.example @@ -13,6 +13,11 @@ OLLAMA_API_KEY=your_ollama_cloud_api_key ATTENDEE_API_KEY= ATTENDEE_API_URL=https://api.attendee.dev/v1 +WEBHOOK_URL=https://your-ngrok-url/webhook + +# STT & TTS +DEEPGRAM_API_KEY=your_deepgram_api_key +TTS_API_KEY= # --- Native run --- # DATABASE_URL=postgresql://ezscreen_user:CHANGE_ME@127.0.0.1:5432/ezscreen_db diff --git a/services/ai-core-services/src/api/v1/meeting_bot.py b/services/ai-core-services/src/api/v1/meeting_bot.py index 353eb22..cbbc666 100644 --- a/services/ai-core-services/src/api/v1/meeting_bot.py +++ b/services/ai-core-services/src/api/v1/meeting_bot.py @@ -1,5 +1,5 @@ -from fastapi import APIRouter, HTTPException, status -from src.meeting_bot.schemas import DispatchBotRequest, DispatchBotResponse, BotStatusResponse +from fastapi import APIRouter, HTTPException, status, Response +from src.meeting_bot.schemas import DispatchBotRequest, DispatchBotResponse, BotStatusResponse, LeaveBotResponse from src.meeting_bot.client import bot_client router = APIRouter(prefix="/screening/bot", tags=["Meeting Bot Service"]) @@ -27,3 +27,33 @@ async def get_bot_status(bot_id: str): status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to fetch bot status: {str(err)}" ) + + +@router.post("/{bot_id}/leave", response_model=LeaveBotResponse, status_code=status.HTTP_200_OK) +async def leave_bot(bot_id: str): + """Instruct the meeting bot to gracefully leave the meeting.""" + try: + return await bot_client.leave_bot(bot_id) + except Exception as err: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Failed to instruct bot to leave: {str(err)}" + ) + + +@router.delete("/{bot_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_bot(bot_id: str): + """Delete a bot record from Attendee.""" + try: + success = await bot_client.delete_bot(bot_id) + if not success: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Failed to delete bot" + ) + return Response(status_code=status.HTTP_204_NO_CONTENT) + except Exception as err: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Failed to delete bot: {str(err)}" + ) diff --git a/services/ai-core-services/src/core/config.py b/services/ai-core-services/src/core/config.py index 1d3f827..94beba8 100644 --- a/services/ai-core-services/src/core/config.py +++ b/services/ai-core-services/src/core/config.py @@ -33,6 +33,15 @@ class Settings(BaseSettings): # Real-time WebSocket Audio Stream Endpoint websocket_url: str + + # Webhook callback URL for Attendee to post lifecycle events to + webhook_url: str + + # Deepgram STT + deepgram_api_key: str + + # TTS API (can be None if relying on the same Deepgram key for Aura) + tts_api_key: str | None = None model_config = SettingsConfigDict( env_file=(str(BASE_DIR / ".env"), str(BASE_DIR.parent.parent / ".env")), diff --git a/services/ai-core-services/src/meeting_bot/attendee.py b/services/ai-core-services/src/meeting_bot/attendee.py index f52b66e..372ae6c 100644 --- a/services/ai-core-services/src/meeting_bot/attendee.py +++ b/services/ai-core-services/src/meeting_bot/attendee.py @@ -6,9 +6,11 @@ from src.meeting_bot.schemas import ( AttendeeAudioSettings, AttendeeWebsocketSettings, + AttendeeWebhookConfig, AttendeeScheduleBotRequest, AttendeeScheduleBotResponse, AttendeeBotStatusResponse, + LeaveBotResponse, ) @@ -34,6 +36,11 @@ async def schedule_bot(self, request: AttendeeScheduleBotRequest) -> AttendeeSch sample_rate=24000 ) ) + + if not request.webhooks and hasattr(settings, "webhook_url") and settings.webhook_url: + request.webhooks = [ + AttendeeWebhookConfig(url=settings.webhook_url) + ] bot_payload = request.model_dump(exclude_none=True) @@ -108,7 +115,42 @@ async def get_bot(self, bot_id: str) -> AttendeeBotStatusResponse: except Exception as err: logger.warning("Could not fetch bot details from Attendee API", extra={"bot_id": bot_id, "error": str(err)}) - return fallback_response + async def leave_bot(self, bot_id: str) -> LeaveBotResponse: + """Instruct the bot to gracefully leave the meeting.""" + if not self.api_key: + return LeaveBotResponse(bot_id=bot_id, status="leaving") + + try: + async with httpx.AsyncClient(timeout=10.0) as client: + resp = await client.post( + f"{self.api_url}/api/v1/bots/{bot_id}/leave", + headers=self._get_headers() + ) + if resp.status_code in (200, 202): + return LeaveBotResponse(bot_id=bot_id, status="leaving") + else: + logger.warning("Failed to instruct bot to leave", extra={"bot_id": bot_id, "status": resp.status_code}) + except Exception as err: + logger.error("Error communicating with Attendee API for leave_bot", extra={"error": str(err)}) + + return LeaveBotResponse(bot_id=bot_id, status="error") + + async def delete_bot(self, bot_id: str) -> bool: + """Delete the bot record from Attendee.""" + if not self.api_key: + return True + + try: + async with httpx.AsyncClient(timeout=10.0) as client: + resp = await client.delete( + f"{self.api_url}/api/v1/bots/{bot_id}", + headers=self._get_headers() + ) + return resp.status_code in (200, 204) + except Exception as err: + logger.error("Error communicating with Attendee API for delete_bot", extra={"error": str(err)}) + + return False attendee_client = AttendeeApiClient() diff --git a/services/ai-core-services/src/meeting_bot/client.py b/services/ai-core-services/src/meeting_bot/client.py index d236ba0..61b8f21 100644 --- a/services/ai-core-services/src/meeting_bot/client.py +++ b/services/ai-core-services/src/meeting_bot/client.py @@ -9,6 +9,7 @@ DispatchBotRequest, DispatchBotResponse, BotStatusResponse, + LeaveBotResponse, ) @@ -66,5 +67,13 @@ async def get_bot_status(self, bot_id: str) -> BotStatusResponse: duration_seconds=attendee_res.duration_seconds, ) + async def leave_bot(self, bot_id: str) -> LeaveBotResponse: + """Instruct the bot to leave the meeting.""" + return await attendee_client.leave_bot(bot_id) + + async def delete_bot(self, bot_id: str) -> bool: + """Delete the bot from Attendee.""" + return await attendee_client.delete_bot(bot_id) + bot_client = AttendeeBotClient() diff --git a/services/ai-core-services/src/meeting_bot/schemas.py b/services/ai-core-services/src/meeting_bot/schemas.py index 968c76d..6dae324 100644 --- a/services/ai-core-services/src/meeting_bot/schemas.py +++ b/services/ai-core-services/src/meeting_bot/schemas.py @@ -11,12 +11,23 @@ class AttendeeWebsocketSettings(BaseModel): audio: AttendeeAudioSettings +class AttendeeWebhookConfig(BaseModel): + url: str + triggers: list[str] = ["bot.state_change", "participant_events.speech_start_stop"] + + class AttendeeScheduleBotRequest(BaseModel): meeting_url: str bot_name: str = "ezscreener" join_at: Optional[str] = None transcription_settings: Dict[str, Any] = Field(default_factory=dict) websocket_settings: Optional[AttendeeWebsocketSettings] = None + webhooks: Optional[list[AttendeeWebhookConfig]] = None + + +class LeaveBotResponse(BaseModel): + bot_id: str + status: str = "leaving" class AttendeeScheduleBotResponse(BaseModel): From 19cf32e4f964ca3a5767408d041782777893ac05 Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Mon, 31 Aug 2026 11:28:17 +0530 Subject: [PATCH 2/8] implement read-only Attendee webhooks and dispatch --- services/ai-core-services/.env.example | 2 + services/ai-core-services/src/core/config.py | 3 + services/ai-core-services/src/main.py | 3 + .../src/meeting_bot/repository.py | 19 ++++ .../src/screening_pipeline/__init__.py | 1 + .../src/screening_pipeline/webhook_handler.py | 92 +++++++++++++++++++ 6 files changed, 120 insertions(+) create mode 100644 services/ai-core-services/src/screening_pipeline/__init__.py create mode 100644 services/ai-core-services/src/screening_pipeline/webhook_handler.py diff --git a/services/ai-core-services/.env.example b/services/ai-core-services/.env.example index 222fe67..dccd230 100644 --- a/services/ai-core-services/.env.example +++ b/services/ai-core-services/.env.example @@ -11,6 +11,8 @@ OLLAMA_URL=https://ollama.com OLLAMA_MODEL=gemma4:31b OLLAMA_API_KEY=your_ollama_cloud_api_key +CORE_API_URL=http://localhost:8000 + ATTENDEE_API_KEY= ATTENDEE_API_URL=https://api.attendee.dev/v1 WEBHOOK_URL=https://your-ngrok-url/webhook diff --git a/services/ai-core-services/src/core/config.py b/services/ai-core-services/src/core/config.py index 94beba8..2a8e127 100644 --- a/services/ai-core-services/src/core/config.py +++ b/services/ai-core-services/src/core/config.py @@ -37,6 +37,9 @@ class Settings(BaseSettings): # Webhook callback URL for Attendee to post lifecycle events to webhook_url: str + # Internal URL for Core API (to update DB state) + core_api_url: str + # Deepgram STT deepgram_api_key: str diff --git a/services/ai-core-services/src/main.py b/services/ai-core-services/src/main.py index c8fa0c1..2b31d36 100644 --- a/services/ai-core-services/src/main.py +++ b/services/ai-core-services/src/main.py @@ -5,6 +5,8 @@ from src.api.v1 import matching from src.api.v1 import question_generation from src.api.v1.meeting_bot import router as meeting_bot_router +from src.screening_pipeline.webhook_handler import router as webhook_router + app = FastAPI( title="EZScreen Service", description="Unified AI Microservice hosting Parsing, Matching, Screening, Attendee Bot, and Interview Analysis modules", @@ -34,3 +36,4 @@ def health_check(): app.include_router(matching.router, prefix="/internal/v1/match") app.include_router(question_generation.router, prefix="/internal/v1/screening/questions") app.include_router(meeting_bot_router) +app.include_router(webhook_router) diff --git a/services/ai-core-services/src/meeting_bot/repository.py b/services/ai-core-services/src/meeting_bot/repository.py index c6e9fc7..c666d7b 100644 --- a/services/ai-core-services/src/meeting_bot/repository.py +++ b/services/ai-core-services/src/meeting_bot/repository.py @@ -36,5 +36,24 @@ async def get_by_id(self, session_id: str) -> Optional[InterviewSessionDetailRes ) return None + async def get_by_bot_id(self, bot_id: str) -> Optional[InterviewSessionDetailResponse]: + """Fetch session by bot_id inside interview_metadata.""" + try: + db_obj = None + session = AsyncSessionLocal() + async with session: + query = select(DBInterviewSession).where( + DBInterviewSession.interview_metadata['bot_id'].astext == bot_id + ) + result = await session.execute(query) + db_obj = result.scalar_one_or_none() + + if not db_obj: + return None + return db_obj.to_response() + except Exception as err: + logger.error("Repository failed to fetch session by bot_id", extra={"bot_id": bot_id, "error": str(err)}) + return None + interview_session_repo = InterviewSessionRepository() diff --git a/services/ai-core-services/src/screening_pipeline/__init__.py b/services/ai-core-services/src/screening_pipeline/__init__.py new file mode 100644 index 0000000..34253dd --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/__init__.py @@ -0,0 +1 @@ +# Screening pipeline module diff --git a/services/ai-core-services/src/screening_pipeline/webhook_handler.py b/services/ai-core-services/src/screening_pipeline/webhook_handler.py new file mode 100644 index 0000000..7baef83 --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/webhook_handler.py @@ -0,0 +1,92 @@ +import httpx +from fastapi import APIRouter, Request, status, BackgroundTasks +from typing import Dict, Any +from src.core.logger import logger +from src.core.config import settings +from src.meeting_bot.repository import interview_session_repo + +router = APIRouter(tags=["Attendee Webhooks"]) + +async def update_session_status(session_id: str, new_status: str): + """Make an internal API call to core-api to update session status.""" + try: + # Assuming core-api is listening at settings.core_api_url + url = f"{settings.core_api_url.rstrip('/')}/api/v1/interview-sessions/{session_id}/status" + payload = {"status": new_status} + + async with httpx.AsyncClient(timeout=5.0) as client: + resp = await client.patch(url, json=payload) + if resp.status_code not in (200, 204): + logger.error("Failed to update status via core-api", extra={"status_code": resp.status_code, "body": resp.text}) + except Exception as err: + logger.error("Error calling core-api for status update", extra={"error": str(err)}) + +async def process_state_change(payload: Dict[str, Any]): + """Background task to handle bot state changes.""" + data = payload.get("data", {}) + bot_id = data.get("bot_id") + new_state = data.get("state") + + if not bot_id or not new_state: + logger.warning("Webhook missing bot_id or state", extra={"payload": payload}) + return + + session = await interview_session_repo.get_by_bot_id(bot_id) + if not session: + logger.warning("Received webhook for unknown bot", extra={"bot_id": bot_id}) + return + + logger.info("Bot state changed", extra={"bot_id": bot_id, "new_state": new_state, "session_id": session.id}) + + # State machine logic + if new_state == "joined_recording": + # Bot successfully entered the meeting + await update_session_status(session.id, "in_progress") + # TODO: Trigger orchestrator start sequence (greeting) + pass + + elif new_state in ["ended", "left"]: + # Bot left the meeting or it concluded + await update_session_status(session.id, "completed") + # TODO: Trigger final LLM summary & store to interview_analysis + pass + + elif new_state == "fatal_error": + # Bot crashed or failed to join + await update_session_status(session.id, "failed") + + +async def process_participant_event(payload: Dict[str, Any]): + """Background task to handle participant events (like barge-in).""" + data = payload.get("data", {}) + bot_id = data.get("bot_id") + event = data.get("event") + + if event == "speech_started": + # TODO: Signal the TTS engine / orchestrator to STOP speaking (barge-in) + logger.debug("Barge-in detected via webhook", extra={"bot_id": bot_id}) + + +@router.post("/webhook", status_code=status.HTTP_200_OK) +async def handle_attendee_webhook(request: Request, background_tasks: BackgroundTasks): + """ + Receives all lifecycle and participant events from Attendee.dev. + Uses BackgroundTasks so Attendee gets an immediate 200 OK. + """ + try: + payload = await request.json() + trigger = payload.get("trigger") + + if trigger == "bot.state_change": + background_tasks.add_task(process_state_change, payload) + + elif trigger == "participant_events.speech_start_stop": + background_tasks.add_task(process_participant_event, payload) + + else: + logger.debug("Ignored webhook trigger", extra={"trigger": trigger}) + + except Exception as err: + logger.error("Error parsing webhook", extra={"error": str(err)}) + + return {"status": "received"} From 9b470fdf37bda3754d76f552449cd0e0c6d3bba2 Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Mon, 31 Aug 2026 11:55:02 +0530 Subject: [PATCH 3/8] feat: implement screening pipeline orchestrator --- services/ai-core-services/src/main.py | 2 + .../src/screening_pipeline/audio_websocket.py | 69 +++++++ .../src/screening_pipeline/orchestrator.py | 180 ++++++++++++++++++ .../src/screening_pipeline/stt_client.py | 27 +++ .../src/screening_pipeline/tts_client.py | 20 ++ services/ai-core-services/test_websocket.py | 49 +++++ 6 files changed, 347 insertions(+) create mode 100644 services/ai-core-services/src/screening_pipeline/audio_websocket.py create mode 100644 services/ai-core-services/src/screening_pipeline/orchestrator.py create mode 100644 services/ai-core-services/src/screening_pipeline/stt_client.py create mode 100644 services/ai-core-services/src/screening_pipeline/tts_client.py create mode 100644 services/ai-core-services/test_websocket.py diff --git a/services/ai-core-services/src/main.py b/services/ai-core-services/src/main.py index 2b31d36..d2231dc 100644 --- a/services/ai-core-services/src/main.py +++ b/services/ai-core-services/src/main.py @@ -6,6 +6,7 @@ from src.api.v1 import question_generation from src.api.v1.meeting_bot import router as meeting_bot_router from src.screening_pipeline.webhook_handler import router as webhook_router +from src.screening_pipeline.audio_websocket import router as websocket_router app = FastAPI( title="EZScreen Service", @@ -37,3 +38,4 @@ def health_check(): app.include_router(question_generation.router, prefix="/internal/v1/screening/questions") app.include_router(meeting_bot_router) app.include_router(webhook_router) +app.include_router(websocket_router) diff --git a/services/ai-core-services/src/screening_pipeline/audio_websocket.py b/services/ai-core-services/src/screening_pipeline/audio_websocket.py new file mode 100644 index 0000000..154b3df --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/audio_websocket.py @@ -0,0 +1,69 @@ +import base64 +from fastapi import APIRouter, WebSocket, WebSocketDisconnect +from typing import Dict, Any +from src.core.logger import logger + +router = APIRouter(tags=["Attendee WebSocket"]) + +# We will manage active sessions here +active_sessions: Dict[str, Any] = {} + +async def speak_to_attendee(websocket: WebSocket, pcm_bytes: bytes): + """Utility to chunk and send PCM audio to Attendee.""" + # Chunk PCM bytes into 2400-byte frames (50ms at 24kHz) + chunk_size = 2400 + for i in range(0, len(pcm_bytes), chunk_size): + chunk = pcm_bytes[i:i + chunk_size] + await websocket.send_json({ + "trigger": "realtime_audio.bot_output", + "data": { + "chunk": base64.b64encode(chunk).decode('utf-8'), + "sample_rate": 24000 + } + }) + +@router.websocket("/attendee-websocket") +async def attendee_audio_ws(websocket: WebSocket): + """ + WebSocket endpoint for bidirectional audio streaming. + Attendee will connect to this URL. + """ + await websocket.accept() + logger.info("Attendee connected to WebSocket") + + bot_id = None + + try: + while True: + message = await websocket.receive_json() + trigger = message.get("trigger") + data = message.get("data", {}) + + if trigger == "realtime_audio.mixed": + # Inbound audio from candidate + chunk_b64 = data.get("chunk") + if chunk_b64 and bot_id and bot_id in active_sessions: + pcm_bytes = base64.b64decode(chunk_b64) + orchestrator = active_sessions[bot_id] + await orchestrator.stt_client.send_audio(pcm_bytes) + + elif trigger == "bot.joined": + # Initial payload sent upon connection + bot_id = data.get("bot_id") + + from src.screening_pipeline.orchestrator import InterviewOrchestrator + orchestrator = InterviewOrchestrator(bot_id=bot_id, websocket=websocket) + active_sessions[bot_id] = orchestrator + + logger.info("Bot audio stream initialized", extra={"bot_id": bot_id}) + + # Start the orchestrator logic + import asyncio + asyncio.create_task(orchestrator.start()) + + except WebSocketDisconnect: + logger.info("Attendee WebSocket disconnected", extra={"bot_id": bot_id}) + finally: + if bot_id and bot_id in active_sessions: + orchestrator = active_sessions.pop(bot_id) + await orchestrator.cleanup() diff --git a/services/ai-core-services/src/screening_pipeline/orchestrator.py b/services/ai-core-services/src/screening_pipeline/orchestrator.py new file mode 100644 index 0000000..568f651 --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/orchestrator.py @@ -0,0 +1,180 @@ +import asyncio +from typing import Dict, Any +from src.core.logger import logger +from src.meeting_bot.repository import interview_session_repo +from src.screening_pipeline.stt_client import DeepgramSTTClient +from src.screening_pipeline.tts_client import TTSClient +from src.core.config import settings + +class InterviewOrchestrator: + """ + Manages the state machine for the AI interview. + Controls the STT, Intent Router, LLM Evaluator, and TTS modules. + """ + + def __init__(self, bot_id: str, websocket): + self.bot_id = bot_id + self.websocket = websocket + self.session: Any = None + self.questions: list = [] + self.current_question_idx = 0 + self.is_active = False + + self.stt_client = DeepgramSTTClient( + api_key=settings.deepgram_api_key, + on_transcript=self.handle_candidate_speech + ) + self.tts_client = TTSClient(api_key=settings.tts_api_key or "") + + # State tracking for the Q&A loop + self.current_interaction_state = "idle" # idle, speaking, listening, evaluating + self.transcript_log: list = [] + + async def append_transcript_to_db(self, interaction: Dict[str, Any]): + """Makes an internal call to core-api to append a single interaction to the DB.""" + import httpx + from src.core.config import settings + + try: + url = f"{settings.core_api_url.rstrip('/')}/api/v1/interview-sessions/{self.session.id}/transcript" + async with httpx.AsyncClient(timeout=5.0) as client: + resp = await client.post(url, json=interaction) + if resp.status_code not in (200, 201, 204): + logger.error("Failed to incrementally save transcript", extra={"status": resp.status_code}) + except Exception as err: + logger.error("Error communicating with core-api to save transcript", extra={"error": str(err)}) + + async def start(self): + """Initializes the interview session and speaks the greeting.""" + logger.info(f"Orchestrator starting for bot {self.bot_id}") + self.session = await interview_session_repo.get_by_bot_id(self.bot_id) + + # MOCK FALLBACK FOR LOCAL TESTING VIA test_websocket.py + if not self.session and self.bot_id == "test_bot_123": + logger.warning("Using mock session for local test_websocket.py execution!") + class MockSession: + id = "00000000-0000-0000-0000-000000000000" + generated_questions = [ + {"id": 1, "question": "Mock question 1: Can you explain HashMap?"}, + {"id": 2, "question": "Mock question 2: What is Docker?"} + ] + self.session = MockSession() + + if not self.session: + logger.error("Could not find session for bot", extra={"bot_id": self.bot_id}) + return + + # Fetch the pre-generated questions and randomize their order + import random + self.questions = self.session.generated_questions or [] + random.shuffle(self.questions) + + self.is_active = True + + await self.stt_client.connect() + + # 1. State 1: The Start (greeting) + greeting_text = "Hello! I'm your AI interviewer today. Let's begin with a few technical questions." + interaction = { + "interaction_type": "greeting", + "bot_speech": greeting_text, + "candidate_answer": "" + } + self.transcript_log.append(interaction) + await self.append_transcript_to_db(interaction) + + await self.speak(greeting_text) + self.current_interaction_state = "listening" + + def handle_candidate_speech(self, transcript: str): + """Callback from STT when the candidate finishes a thought.""" + if not self.is_active or self.current_interaction_state != "listening": + return + + logger.info(f"Candidate said: {transcript}") + self.current_interaction_state = "evaluating" + + # We spawn an async task so we don't block the STT thread + asyncio.create_task(self.process_intent_and_evaluate(transcript)) + + async def process_intent_and_evaluate(self, transcript: str): + """ + Runs the text through the Intent Router, and either evaluates it + or handles it conversationally (clarifications, small talk). + """ + logger.debug("Routing intent...") + + # TODO: Call Gemma4:31b Intent Router here + intent = "ANSWERING" # Mocked intent + + if intent == "ANSWERING": + # Record the answer in the transcript log + if self.transcript_log: + self.transcript_log[-1]["candidate_answer"] = transcript + # Re-sync the completed interaction block to the DB + await self.append_transcript_to_db(self.transcript_log[-1]) + + # TODO: Call Gemma4:31b Evaluation prompt + + # For now, just move to next question + self.current_question_idx += 1 + await self.ask_next_question() + + elif intent in ["CLARIFICATION", "SMALL_TALK"]: + # TODO: Add follow_up object to transcript_log, synthesize bot_response + pass + + elif intent == "SKIP": + self.current_question_idx += 1 + await self.ask_next_question() + + async def ask_next_question(self): + """Moves to the next question in the array or closes the interview.""" + if self.current_question_idx >= len(self.questions): + # State 4: The End (closing) + closing_text = "Thank you for your time today. Our HR team will be in touch shortly." + interaction = { + "interaction_type": "closing", + "bot_speech": closing_text, + "candidate_answer": "" + } + self.transcript_log.append(interaction) + await self.append_transcript_to_db(interaction) + + await self.speak(closing_text) + self.current_interaction_state = "closing" + # We don't need a massive bulk save anymore! Just call Attendee leave_bot here. + return + + question_obj = self.questions[self.current_question_idx] + q_text = question_obj.get("question", "") + + # State 2: Asking from the List + interaction = { + "interaction_type": "question", + "question_id": question_obj.get("id"), + "bot_speech": q_text, + "candidate_answer": "", + "follow_ups": [] + } + self.transcript_log.append(interaction) + await self.append_transcript_to_db(interaction) + + await self.speak(q_text) + self.current_interaction_state = "listening" + + async def speak(self, text: str): + """Synthesizes text and streams it to the WebSocket.""" + self.current_interaction_state = "speaking" + from src.screening_pipeline.audio_websocket import speak_to_attendee + + async for chunk in self.tts_client.synthesize(text): + # If candidate barged in, we would break this loop + if not self.is_active: + break + await speak_to_attendee(self.websocket, chunk) + + async def cleanup(self): + """Teardown connections.""" + self.is_active = False + await self.stt_client.close() diff --git a/services/ai-core-services/src/screening_pipeline/stt_client.py b/services/ai-core-services/src/screening_pipeline/stt_client.py new file mode 100644 index 0000000..eb59660 --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/stt_client.py @@ -0,0 +1,27 @@ +import asyncio +from typing import Callable +from src.core.logger import logger + +class DeepgramSTTClient: + """Streams PCM audio to Deepgram via WebSocket and emits transcription events.""" + + def __init__(self, api_key: str, on_transcript: Callable[[str], None]): + self.api_key = api_key + self.on_transcript = on_transcript + self._is_connected = False + + async def connect(self): + """Establish WebSocket connection to STT namespace.""" + logger.info("Initializing STT connection") + self._is_connected = True + + async def send_audio(self, pcm_bytes: bytes): + """Send raw PCM chunk to STT.""" + if not self._is_connected: + return + pass + + async def close(self): + """Gracefully close STT connection.""" + self._is_connected = False + logger.info("Closed STT connection") diff --git a/services/ai-core-services/src/screening_pipeline/tts_client.py b/services/ai-core-services/src/screening_pipeline/tts_client.py new file mode 100644 index 0000000..1474e55 --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/tts_client.py @@ -0,0 +1,20 @@ +import asyncio +from typing import AsyncGenerator +from src.core.logger import logger + +class TTSClient: + """Generates PCM audio from text using a TTS provider.""" + + def __init__(self, api_key: str): + self.api_key = api_key + + async def synthesize(self, text: str) -> AsyncGenerator[bytes, None]: + """ + Yields raw PCM chunks (24kHz) for the given text. + """ + logger.debug(f"Synthesizing TTS: {text[:30]}...") + + # Stub: just yield a dummy 2400-byte frame of silence to simulate output + dummy_chunk = b'\x00' * 2400 + yield dummy_chunk + await asyncio.sleep(0.05) diff --git a/services/ai-core-services/test_websocket.py b/services/ai-core-services/test_websocket.py new file mode 100644 index 0000000..0b4b4b1 --- /dev/null +++ b/services/ai-core-services/test_websocket.py @@ -0,0 +1,49 @@ +import asyncio +import websockets +import json +import base64 + +async def test_bot_connection(): + uri = "ws://localhost:8002/attendee-websocket" + + print(f"Connecting to {uri}...") + async with websockets.connect(uri) as websocket: + print("Connected!") + + # 1. Simulate Attendee sending the bot.joined event + join_payload = { + "trigger": "bot.joined", + "data": { + "bot_id": "test_bot_123" + } + } + await websocket.send(json.dumps(join_payload)) + print("Sent bot.joined event.") + + # 2. Listen for the Bot's Greeting (TTS output) + while True: + try: + msg = await asyncio.wait_for(websocket.recv(), timeout=2.0) + data = json.loads(msg) + if data.get("trigger") == "realtime_audio.bot_output": + print("Received PCM audio chunk from Bot!") + # In a real test, you'd decode this base64 and play it + # chunk = base64.b64decode(data["data"]["chunk"]) + except asyncio.TimeoutError: + print("No more audio received. Bot is listening...") + break + + # 3. Simulate Candidate speaking (sending dummy PCM audio) + print("Simulating candidate speech...") + dummy_pcm = b'\x00' * 2400 + speech_payload = { + "trigger": "realtime_audio.mixed", + "data": { + "chunk": base64.b64encode(dummy_pcm).decode('utf-8') + } + } + await websocket.send(json.dumps(speech_payload)) + print("Sent mock candidate audio.") + +if __name__ == "__main__": + asyncio.run(test_bot_connection()) From a7176a29d0c90418e1de0181b16624d6da12984e Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Tue, 1 Sep 2026 13:15:22 +0530 Subject: [PATCH 4/8] VAD-based Whisper-Cloud client and integrate intent routing framework --- docker-compose.yml | 2 + docs/architecture/DB_DESIGN.md | 34 +- services/ai-core-services/.env.example | 8 +- services/ai-core-services/pyproject.toml | 1 + services/ai-core-services/src/core/config.py | 10 +- .../src/meeting_bot/client.py | 2 +- .../src/screening_pipeline/audio_websocket.py | 32 +- .../src/screening_pipeline/evaluator.py | 189 +++++++++ .../src/screening_pipeline/orchestrator.py | 361 ++++++++++++------ .../src/screening_pipeline/prompts.py | 54 +++ .../src/screening_pipeline/session_api.py | 117 ++++++ .../src/screening_pipeline/stt_client.py | 130 ++++++- .../src/screening_pipeline/tts_client.py | 56 ++- .../src/screening_pipeline/webhook_handler.py | 2 +- services/ai-core-services/test_websocket.py | 49 --- 15 files changed, 818 insertions(+), 229 deletions(-) create mode 100644 services/ai-core-services/src/screening_pipeline/evaluator.py create mode 100644 services/ai-core-services/src/screening_pipeline/prompts.py create mode 100644 services/ai-core-services/src/screening_pipeline/session_api.py delete mode 100644 services/ai-core-services/test_websocket.py diff --git a/docker-compose.yml b/docker-compose.yml index 9cf4e3c..2c78027 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,6 +84,8 @@ services: - ./services/ai-core-services/.env extra_hosts: - "host.docker.internal:host-gateway" + environment: + CORE_API_URL: "http://core-api:8000" volumes: - rapidocr_models:/usr/local/lib/python3.11/site-packages/rapidocr/models - hf_cache:/root/.cache/huggingface diff --git a/docs/architecture/DB_DESIGN.md b/docs/architecture/DB_DESIGN.md index 337667f..8e914cf 100644 --- a/docs/architecture/DB_DESIGN.md +++ b/docs/architecture/DB_DESIGN.md @@ -490,7 +490,33 @@ This dynamic list of questions is tailored to the candidate and generated by the --- -### Interview Analysis `question_answer` +### Interview Transcript `question_answer` + +```json +[ + { + "question_id": 1, + "bot_speech": "Can you explain the difference between a HashMap and a ConcurrentHashMap?", + "candidate_answer": "A HashMap isn't thread-safe, while a ConcurrentHashMap is designed for multi-threaded environments.", + "follow_ups": [ + { + "bot_speech": "You mentioned it handles synchronization; can you explain how it does that differently?", + "candidate_answer": "It uses a technique called lock stripping." + } + ] + }, + { + "question_id": 2, + "bot_speech": "Can you explain the difference between checked and unchecked exceptions?", + "candidate_answer": "Checked exceptions are checked at compile time, and you have to use a try catch block. Unchecked exceptions extend RuntimeException." + } +] +``` +This stores ONLY the clean, substantive technical Q&A combinations generated during the session. Greetings, closings, and small-talk are excluded to maintain a pristine evaluation transcript. + +--- + +### Full Interview Transcript `interview_metadata` ```json [ @@ -524,7 +550,7 @@ This dynamic list of questions is tailored to the candidate and generated by the } ] ``` -This stores the raw Q&A combinations generated during the session BEFORE the final LLM evaluation. +This stores the FULL conversational log of the interview, including greetings, small-talk, clarifications, and closing statements. It is saved at the end of the session to the `interview_metadata` column. --- @@ -541,10 +567,8 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin "coverage_percent": 75.0, "keywords_found": ["compile-time", "try-catch", "RuntimeException"], "keywords_missing": ["throws"], - "is_sufficient": true, "decision": "NEXT_QUESTION", "feedback": "The candidate provided a solid and accurate definition of the exceptions." - }, { "question_id": 2, @@ -554,7 +578,6 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin "coverage_percent": 66.6, "keywords_found": ["thread-safety", "multi-threading"], "keywords_missing": ["segment locking"], - "is_sufficient": false, "decision": "ASK_FOLLOW_UP", "feedback": "The candidate correctly identified the basic difference regarding thread-safety, but failed to explain *how* it achieves this.", "follow_ups": [ @@ -565,7 +588,6 @@ This stores the raw Q&A combinations generated during the session BEFORE the fin "coverage_percent": 100.0, "keywords_found": ["segment locking", "thread-safety", "multi-threading"], "keywords_missing": [], - "is_sufficient": true, "decision": "NEXT_QUESTION", "feedback": "The candidate correctly identified lock stripping/bucket-level locking as the mechanism to reduce contention." } diff --git a/services/ai-core-services/.env.example b/services/ai-core-services/.env.example index dccd230..ec478e9 100644 --- a/services/ai-core-services/.env.example +++ b/services/ai-core-services/.env.example @@ -17,9 +17,11 @@ ATTENDEE_API_KEY= ATTENDEE_API_URL=https://api.attendee.dev/v1 WEBHOOK_URL=https://your-ngrok-url/webhook -# STT & TTS -DEEPGRAM_API_KEY=your_deepgram_api_key -TTS_API_KEY= +# STT & TTS (Cloud APIs) +WHISPER_API_URL=https://api.groq.com/openai/v1/audio/transcriptions +WHISPER_API_KEY=your_whisper_api_key +KOKORO_API_URL=https://api.replicate.com/v1/predictions +KOKORO_API_KEY=your_kokoro_api_key # --- Native run --- # DATABASE_URL=postgresql://ezscreen_user:CHANGE_ME@127.0.0.1:5432/ezscreen_db diff --git a/services/ai-core-services/pyproject.toml b/services/ai-core-services/pyproject.toml index 12021d9..8393117 100644 --- a/services/ai-core-services/pyproject.toml +++ b/services/ai-core-services/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "python-json-logger>=2.0.7", "docling>=2.15.0", "boto3>=1.34.0", + "webrtcvad>=2.0.10", ] [build-system] diff --git a/services/ai-core-services/src/core/config.py b/services/ai-core-services/src/core/config.py index 2a8e127..231c668 100644 --- a/services/ai-core-services/src/core/config.py +++ b/services/ai-core-services/src/core/config.py @@ -40,11 +40,13 @@ class Settings(BaseSettings): # Internal URL for Core API (to update DB state) core_api_url: str - # Deepgram STT - deepgram_api_key: str + # Whisper STT (Cloud API) + whisper_api_url: str | None = None + whisper_api_key: str | None = None - # TTS API (can be None if relying on the same Deepgram key for Aura) - tts_api_key: str | None = None + # Kokoro TTS (Cloud API) + kokoro_api_url: str | None = None + kokoro_api_key: str | None = None model_config = SettingsConfigDict( env_file=(str(BASE_DIR / ".env"), str(BASE_DIR.parent.parent / ".env")), diff --git a/services/ai-core-services/src/meeting_bot/client.py b/services/ai-core-services/src/meeting_bot/client.py index 61b8f21..902ee58 100644 --- a/services/ai-core-services/src/meeting_bot/client.py +++ b/services/ai-core-services/src/meeting_bot/client.py @@ -37,7 +37,7 @@ async def dispatch_bot(self, request: DispatchBotRequest) -> DispatchBotResponse join_at=scheduled_at, websocket_settings=AttendeeWebsocketSettings( audio=AttendeeAudioSettings( - url=settings.websocket_url, + url=f"{settings.websocket_url.rstrip('/')}/{request.interview_session_id}", sample_rate=24000 ) ) diff --git a/services/ai-core-services/src/screening_pipeline/audio_websocket.py b/services/ai-core-services/src/screening_pipeline/audio_websocket.py index 154b3df..d99fecf 100644 --- a/services/ai-core-services/src/screening_pipeline/audio_websocket.py +++ b/services/ai-core-services/src/screening_pipeline/audio_websocket.py @@ -22,16 +22,23 @@ async def speak_to_attendee(websocket: WebSocket, pcm_bytes: bytes): } }) -@router.websocket("/attendee-websocket") -async def attendee_audio_ws(websocket: WebSocket): +@router.websocket("/attendee-websocket/{bot_id}") +async def attendee_audio_ws(websocket: WebSocket, bot_id: str): """ WebSocket endpoint for bidirectional audio streaming. Attendee will connect to this URL. """ await websocket.accept() - logger.info("Attendee connected to WebSocket") + logger.info("Attendee connected to WebSocket", extra={"bot_id": bot_id}) - bot_id = None + from src.screening_pipeline.orchestrator import InterviewOrchestrator + orchestrator = InterviewOrchestrator(bot_id=bot_id, websocket=websocket) + active_sessions[bot_id] = orchestrator + + logger.info("Bot audio stream initialized", extra={"bot_id": bot_id}) + + import asyncio + asyncio.create_task(orchestrator.start()) try: while True: @@ -42,25 +49,10 @@ async def attendee_audio_ws(websocket: WebSocket): if trigger == "realtime_audio.mixed": # Inbound audio from candidate chunk_b64 = data.get("chunk") - if chunk_b64 and bot_id and bot_id in active_sessions: + if chunk_b64 and bot_id in active_sessions: pcm_bytes = base64.b64decode(chunk_b64) - orchestrator = active_sessions[bot_id] await orchestrator.stt_client.send_audio(pcm_bytes) - elif trigger == "bot.joined": - # Initial payload sent upon connection - bot_id = data.get("bot_id") - - from src.screening_pipeline.orchestrator import InterviewOrchestrator - orchestrator = InterviewOrchestrator(bot_id=bot_id, websocket=websocket) - active_sessions[bot_id] = orchestrator - - logger.info("Bot audio stream initialized", extra={"bot_id": bot_id}) - - # Start the orchestrator logic - import asyncio - asyncio.create_task(orchestrator.start()) - except WebSocketDisconnect: logger.info("Attendee WebSocket disconnected", extra={"bot_id": bot_id}) finally: diff --git a/services/ai-core-services/src/screening_pipeline/evaluator.py b/services/ai-core-services/src/screening_pipeline/evaluator.py new file mode 100644 index 0000000..38cca9c --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/evaluator.py @@ -0,0 +1,189 @@ +""" +Answer Evaluator for the AI Screening Pipeline. +Handles intent routing, answer evaluation, and evaluation result building. +""" +import json +from typing import Dict, Any, Optional, Tuple, List +from src.core.logger import logger +from src.llm.client import OllamaClient +from src.screening_pipeline.prompts import ( + INTENT_ROUTER_SYSTEM, + ANSWER_EVALUATION_SYSTEM, +) + + +class AnswerEvaluator: + """Evaluates candidate answers using LLM-based intent routing and scoring.""" + + def __init__(self, llm_client: OllamaClient): + self.llm_client = llm_client + + async def route_intent(self, current_question: str, transcript: str) -> Tuple[str, str]: + """ + Classifies the candidate's speech into an intent. + Returns: (intent, ai_response) + """ + intent_prompt = f"Current Interview Question: {current_question}\nCandidate Speech: {transcript}" + + try: + intent_res = await self.llm_client.openai_chat_generate( + prompt=intent_prompt, + system=INTENT_ROUTER_SYSTEM, + temperature=0.1 + ) + raw_text = intent_res.response.replace("```json", "").replace("```", "").strip() + data = json.loads(raw_text) + intent = data.get("intent", "ANSWERING") + ai_response = data.get("response", "") + except Exception as e: + logger.error("Intent routing failed", extra={"error": str(e)}) + intent = "ANSWERING" + ai_response = "" + + logger.info("Intent routed", extra={"intent": intent}) + return intent, ai_response + + async def evaluate_answer( + self, + current_question: str, + transcript: str, + expected_keywords: str, + answer_depth: str, + follow_up_context: Optional[List[Dict]] = None + ) -> Dict[str, Any]: + """ + Evaluates the candidate's answer against expected keywords and strictness. + Returns the full eval_data dict from the LLM. + """ + # Build the user prompt + full_context = "You are evaluating a candidate's answer in a FIRST SCREENING interview.\n\n" + + if follow_up_context: + full_context += "NOTE: This is a FOLLOW-UP evaluation. The candidate had an insufficient primary answer.\n\n" + + full_context += f"QUESTION: {current_question}\n" + + candidate_answer = "" + if follow_up_context: + for fu in follow_up_context: + candidate_answer += f"AI: {fu.get('ai_response', '')}\nCandidate: {fu.get('candidate_speech', '')}\n" + candidate_answer += f"Candidate Latest Answer: {transcript}" + + full_context += f"CANDIDATE ANSWER: {candidate_answer}\n" + full_context += f"EXPECTED KEYWORDS (answer should address most of these): {expected_keywords}\n" + full_context += f"EVALUATION STRICTNESS LEVEL: {answer_depth}\n" + + try: + eval_res = await self.llm_client.openai_chat_generate( + prompt=full_context, + system=ANSWER_EVALUATION_SYSTEM, + temperature=0.2 + ) + raw_text = eval_res.response.replace("```json", "").replace("```", "").strip() + eval_data = json.loads(raw_text) + except Exception as e: + logger.error("Answer evaluation failed", extra={"error": str(e)}) + eval_data = { + "score": 0, "coverage_percent": 0, + "keywords_found": [], "keywords_missing": [], + "is_sufficient": False, "decision": "NEXT_QUESTION", + "feedback": "Evaluation failed due to an internal error.", + "suggested_follow_up": "" + } + + decision = eval_data.get("decision", "NEXT_QUESTION") + follow_up_question = eval_data.get("suggested_follow_up", "") + + if decision == "REPEAT_QUESTION": + follow_up_question = "I'm sorry, could you please repeat your answer?" + + logger.info("Answer evaluated", extra={ + "decision": decision, + "score": eval_data.get("score"), + "keywords_missing": eval_data.get("keywords_missing", []), + }) + + return eval_data + + @staticmethod + def build_skip_evaluation(question_obj: Dict, transcript: str) -> Dict[str, Any]: + """Builds a 0-score evaluation for skipped questions.""" + return { + "question_id": question_obj.get("id"), + "question": question_obj.get("question", ""), + "candidate_answer": transcript, + "score": 0, + "coverage_percent": 0, + "keywords_found": [], + "keywords_missing": question_obj.get("expected_keywords", []), + "decision": "NEXT_QUESTION", + "feedback": "Candidate chose to skip this question." + } + + @staticmethod + def build_evaluation_block( + question_obj: Dict, + current_q: str, + transcript: str, + primary_eval: Optional[Dict], + current_eval: Dict, + follow_ups: Optional[List[Dict]] = None + ) -> Dict[str, Any]: + """Builds the final evaluation block for analysis_result.evaluations[].""" + # Use primary_eval for the top-level scores if this was a follow-up completion + source = primary_eval if primary_eval else current_eval + + evaluation = { + "question_id": question_obj.get("id"), + "question": current_q, + "candidate_answer": transcript, + "score": source.get("score", 0), + "coverage_percent": source.get("coverage_percent", 0), + "keywords_found": source.get("keywords_found", []), + "keywords_missing": source.get("keywords_missing", []), + "decision": source.get("decision", "NEXT_QUESTION"), + "feedback": source.get("feedback", "") + } + + # Attach follow-up evaluation data + if follow_ups: + follow_up_data = [] + for fu in follow_ups: + follow_up_data.append({ + "follow_up_question": fu.get("ai_response", ""), + "follow_up_answer": fu.get("candidate_speech", ""), + "score": current_eval.get("score", 0), + "coverage_percent": current_eval.get("coverage_percent", 0), + "keywords_found": current_eval.get("keywords_found", []), + "keywords_missing": current_eval.get("keywords_missing", []), + "decision": current_eval.get("decision", "NEXT_QUESTION"), + "feedback": current_eval.get("feedback", "") + }) + evaluation["follow_ups"] = follow_up_data + + return evaluation + + @staticmethod + def build_qa_entry( + question_obj: Dict, + current_q: str, + transcript: str, + follow_ups: Optional[List[Dict]] = None + ) -> Dict[str, Any]: + """Builds the clean Q&A entry for question_answer column.""" + qa_entry = { + "question_id": question_obj.get("id"), + "bot_speech": current_q, + "candidate_answer": transcript + } + + if follow_ups: + qa_follow_ups = [] + for fu in follow_ups: + qa_follow_ups.append({ + "bot_speech": fu.get("ai_response", ""), + "candidate_answer": fu.get("candidate_speech", "") + }) + qa_entry["follow_ups"] = qa_follow_ups + + return qa_entry diff --git a/services/ai-core-services/src/screening_pipeline/orchestrator.py b/services/ai-core-services/src/screening_pipeline/orchestrator.py index 568f651..ca6b748 100644 --- a/services/ai-core-services/src/screening_pipeline/orchestrator.py +++ b/services/ai-core-services/src/screening_pipeline/orchestrator.py @@ -1,17 +1,32 @@ +""" +Interview Orchestrator — Main state machine for the AI screening interview. +Controls the flow: Greeting → Questions → Evaluation → Follow-up → Closing. +""" import asyncio -from typing import Dict, Any +import random +import json +from typing import Any from src.core.logger import logger -from src.meeting_bot.repository import interview_session_repo -from src.screening_pipeline.stt_client import DeepgramSTTClient -from src.screening_pipeline.tts_client import TTSClient from src.core.config import settings +from src.meeting_bot.repository import interview_session_repo +from src.screening_pipeline.stt_client import WhisperCloudSTTClient +from src.screening_pipeline.tts_client import KokoroCloudTTSClient +from src.screening_pipeline.evaluator import AnswerEvaluator +from src.screening_pipeline.session_api import SessionApiClient +from src.screening_pipeline.prompts import ( + GREETING_TEXT, + CLOSING_TEXT, + MAX_FOLLOW_UPS_PER_QUESTION, +) +from src.llm.client import OllamaClient + class InterviewOrchestrator: """ Manages the state machine for the AI interview. - Controls the STT, Intent Router, LLM Evaluator, and TTS modules. + Coordinates STT, Intent Router, LLM Evaluator, TTS, and Core-API persistence. """ - + def __init__(self, bot_id: str, websocket): self.bot_id = bot_id self.websocket = websocket @@ -19,162 +34,264 @@ def __init__(self, bot_id: str, websocket): self.questions: list = [] self.current_question_idx = 0 self.is_active = False - - self.stt_client = DeepgramSTTClient( - api_key=settings.deepgram_api_key, + + # Audio clients + self.stt_client = WhisperCloudSTTClient( + api_url=settings.whisper_api_url, + api_key=settings.whisper_api_key, on_transcript=self.handle_candidate_speech ) - self.tts_client = TTSClient(api_key=settings.tts_api_key or "") - - # State tracking for the Q&A loop - self.current_interaction_state = "idle" # idle, speaking, listening, evaluating + self.tts_client = KokoroCloudTTSClient( + api_url=settings.kokoro_api_url, + api_key=settings.kokoro_api_key + ) + + # AI modules + llm_client = OllamaClient() + self.evaluator = AnswerEvaluator(llm_client) + self.api_client: SessionApiClient = None # Initialized after session is loaded + + # State tracking + self.current_interaction_state = "idle" # idle, speaking, listening, evaluating self.transcript_log: list = [] + self.analysis_evaluations: list = [] - async def append_transcript_to_db(self, interaction: Dict[str, Any]): - """Makes an internal call to core-api to append a single interaction to the DB.""" - import httpx - from src.core.config import settings - - try: - url = f"{settings.core_api_url.rstrip('/')}/api/v1/interview-sessions/{self.session.id}/transcript" - async with httpx.AsyncClient(timeout=5.0) as client: - resp = await client.post(url, json=interaction) - if resp.status_code not in (200, 201, 204): - logger.error("Failed to incrementally save transcript", extra={"status": resp.status_code}) - except Exception as err: - logger.error("Error communicating with core-api to save transcript", extra={"error": str(err)}) + # ──────────────────────────── LIFECYCLE ──────────────────────────── async def start(self): """Initializes the interview session and speaks the greeting.""" - logger.info(f"Orchestrator starting for bot {self.bot_id}") + logger.info("Orchestrator starting", extra={"bot_id": self.bot_id}) self.session = await interview_session_repo.get_by_bot_id(self.bot_id) - - # MOCK FALLBACK FOR LOCAL TESTING VIA test_websocket.py - if not self.session and self.bot_id == "test_bot_123": - logger.warning("Using mock session for local test_websocket.py execution!") - class MockSession: - id = "00000000-0000-0000-0000-000000000000" - generated_questions = [ - {"id": 1, "question": "Mock question 1: Can you explain HashMap?"}, - {"id": 2, "question": "Mock question 2: What is Docker?"} - ] - self.session = MockSession() - + if not self.session: - logger.error("Could not find session for bot", extra={"bot_id": self.bot_id}) + logger.error("No session found for bot. Cannot start interview.", extra={"bot_id": self.bot_id}) return - - # Fetch the pre-generated questions and randomize their order - import random + + # Initialize the API client with the real session ID + self.api_client = SessionApiClient(session_id=str(self.session.id)) + + # Fetch questions and randomize their order self.questions = self.session.generated_questions or [] random.shuffle(self.questions) - self.is_active = True - + await self.stt_client.connect() - - # 1. State 1: The Start (greeting) - greeting_text = "Hello! I'm your AI interviewer today. Let's begin with a few technical questions." - interaction = { + + # Greeting + self.transcript_log.append({ "interaction_type": "greeting", - "bot_speech": greeting_text, + "bot_speech": GREETING_TEXT, "candidate_answer": "" - } - self.transcript_log.append(interaction) - await self.append_transcript_to_db(interaction) - - await self.speak(greeting_text) + }) + await self.speak(GREETING_TEXT) self.current_interaction_state = "listening" + async def cleanup(self): + """Teardown connections.""" + self.is_active = False + await self.stt_client.close() + + # ──────────────────────────── STT CALLBACK ──────────────────────────── + def handle_candidate_speech(self, transcript: str): - """Callback from STT when the candidate finishes a thought.""" + """Callback from STT when the candidate finishes speaking.""" if not self.is_active or self.current_interaction_state != "listening": return - - logger.info(f"Candidate said: {transcript}") + + logger.info("Candidate speech received", extra={"transcript": transcript}) self.current_interaction_state = "evaluating" - - # We spawn an async task so we don't block the STT thread - asyncio.create_task(self.process_intent_and_evaluate(transcript)) - - async def process_intent_and_evaluate(self, transcript: str): - """ - Runs the text through the Intent Router, and either evaluates it - or handles it conversationally (clarifications, small talk). - """ - logger.debug("Routing intent...") - - # TODO: Call Gemma4:31b Intent Router here - intent = "ANSWERING" # Mocked intent - - if intent == "ANSWERING": - # Record the answer in the transcript log + asyncio.create_task(self._process_speech(transcript)) + + # ──────────────────────────── MAIN PROCESSING ──────────────────────────── + + async def _process_speech(self, transcript: str): + """Routes the candidate's speech through intent detection and evaluation.""" + + # If candidate is responding to the greeting, just move to first question + if self.transcript_log and self.transcript_log[-1].get("interaction_type") == "greeting": + self.transcript_log[-1]["candidate_answer"] = transcript + await self._ask_next_question() + return + + question_obj = self.questions[self.current_question_idx] + current_q = question_obj.get("question", "") + + # ── Step 1: Intent Routing ── + intent, ai_response = await self.evaluator.route_intent(current_q, transcript) + + if intent in ["CLARIFICATION", "SMALL_TALK"]: + await self._handle_conversational(transcript, ai_response) + return + + if intent == "SKIP": + await self._handle_skip(question_obj, current_q, transcript) + return + + # ── Step 2: Answer Evaluation ── + await self._handle_answer(question_obj, current_q, transcript) + + # ──────────────────────────── INTENT HANDLERS ──────────────────────────── + + async def _handle_conversational(self, transcript: str, ai_response: str): + """Handles CLARIFICATION and SMALL_TALK intents.""" + if not ai_response.strip(): + ai_response = "Okay, sounds good." + + if self.transcript_log: + self.transcript_log[-1].setdefault("follow_ups", []).append({ + "candidate_speech": transcript, + "ai_response": ai_response + }) + + await self.speak(ai_response) + self.current_interaction_state = "listening" + + async def _handle_skip(self, question_obj: dict, current_q: str, transcript: str): + """Handles SKIP intent — saves 0-score evaluation and moves on.""" + if self.transcript_log: + self.transcript_log[-1]["candidate_answer"] = transcript + + # Save clean Q&A transcript + qa_entry = AnswerEvaluator.build_qa_entry(question_obj, current_q, transcript) + await self.api_client.save_transcript(qa_entry) + + # Save 0-score evaluation + skip_eval = AnswerEvaluator.build_skip_evaluation(question_obj, transcript) + self.analysis_evaluations.append(skip_eval) + await self.api_client.save_evaluation(skip_eval) + + self.current_question_idx += 1 + await self._ask_next_question() + + async def _handle_answer(self, question_obj: dict, current_q: str, transcript: str): + """Handles ANSWERING intent — evaluates and decides follow-up or next question.""" + + # Determine keywords: use missing keywords from primary eval for follow-up + primary_eval_data = None + if self.transcript_log and self.transcript_log[-1].get("primary_eval"): + primary_eval_data = self.transcript_log[-1]["primary_eval"] + expected_keywords = ", ".join(primary_eval_data.get("keywords_missing", [])) + answer_depth = "partial_depth" + else: + expected_keywords = ", ".join(question_obj.get("expected_keywords", [])) + answer_depth = question_obj.get("answer_depth", "partial_depth") + + # Get follow-up context if exists + follow_up_context = None + if self.transcript_log and self.transcript_log[-1].get("follow_ups"): + follow_up_context = self.transcript_log[-1]["follow_ups"] + + # Evaluate answer via LLM + eval_data = await self.evaluator.evaluate_answer( + current_question=current_q, + transcript=transcript, + expected_keywords=expected_keywords, + answer_depth=answer_depth, + follow_up_context=follow_up_context + ) + + decision = eval_data.get("decision", "NEXT_QUESTION") + is_complete = (decision == "NEXT_QUESTION") + follow_up_question = eval_data.get("suggested_follow_up", "") + + if decision == "REPEAT_QUESTION": + is_complete = False + follow_up_question = "I'm sorry, could you please repeat your answer?" + + # Enforce follow-up limit + current_follow_ups = self.transcript_log[-1].get("follow_ups", []) if self.transcript_log else [] + if len(current_follow_ups) >= MAX_FOLLOW_UPS_PER_QUESTION and not is_complete: + logger.info("Follow-up limit reached, forcing completion") + is_complete = True + follow_up_question = "" + + if not is_complete and follow_up_question: + # Ask follow-up question if self.transcript_log: - self.transcript_log[-1]["candidate_answer"] = transcript - # Re-sync the completed interaction block to the DB - await self.append_transcript_to_db(self.transcript_log[-1]) - - # TODO: Call Gemma4:31b Evaluation prompt - - # For now, just move to next question - self.current_question_idx += 1 - await self.ask_next_question() - - elif intent in ["CLARIFICATION", "SMALL_TALK"]: - # TODO: Add follow_up object to transcript_log, synthesize bot_response - pass - - elif intent == "SKIP": - self.current_question_idx += 1 - await self.ask_next_question() - - async def ask_next_question(self): - """Moves to the next question in the array or closes the interview.""" + self.transcript_log[-1]["primary_eval"] = eval_data + self.transcript_log[-1].setdefault("follow_ups", []).append({ + "candidate_speech": transcript, + "ai_response": follow_up_question + }) + await self.speak(follow_up_question) + self.current_interaction_state = "listening" + else: + # Question complete — persist to DB + await self._complete_question(question_obj, current_q, transcript, primary_eval_data, eval_data) + + async def _complete_question( + self, question_obj: dict, current_q: str, transcript: str, + primary_eval: dict, current_eval: dict + ): + """Saves the completed question's transcript and evaluation to core-api.""" + if self.transcript_log: + self.transcript_log[-1]["candidate_answer"] = transcript + + follow_ups = self.transcript_log[-1].get("follow_ups") if self.transcript_log else None + + # Save clean Q&A transcript (with nested follow-ups) + qa_entry = AnswerEvaluator.build_qa_entry(question_obj, current_q, transcript, follow_ups) + await self.api_client.save_transcript(qa_entry) + + # Save evaluation (with nested follow-up scores) + evaluation = AnswerEvaluator.build_evaluation_block( + question_obj, current_q, transcript, + primary_eval, current_eval, follow_ups + ) + self.analysis_evaluations.append(evaluation) + await self.api_client.save_evaluation(evaluation) + + self.current_question_idx += 1 + await self._ask_next_question() + + # ──────────────────────────── QUESTION FLOW ──────────────────────────── + + async def _ask_next_question(self): + """Moves to the next question or closes the interview.""" if self.current_question_idx >= len(self.questions): - # State 4: The End (closing) - closing_text = "Thank you for your time today. Our HR team will be in touch shortly." - interaction = { - "interaction_type": "closing", - "bot_speech": closing_text, - "candidate_answer": "" - } - self.transcript_log.append(interaction) - await self.append_transcript_to_db(interaction) - - await self.speak(closing_text) - self.current_interaction_state = "closing" - # We don't need a massive bulk save anymore! Just call Attendee leave_bot here. + await self._close_interview() return question_obj = self.questions[self.current_question_idx] q_text = question_obj.get("question", "") - - # State 2: Asking from the List - interaction = { + + self.transcript_log.append({ "interaction_type": "question", "question_id": question_obj.get("id"), "bot_speech": q_text, "candidate_answer": "", "follow_ups": [] - } - self.transcript_log.append(interaction) - await self.append_transcript_to_db(interaction) - + }) + await self.speak(q_text) self.current_interaction_state = "listening" + async def _close_interview(self): + """Speaks closing message and saves the final summary.""" + self.transcript_log.append({ + "interaction_type": "closing", + "bot_speech": CLOSING_TEXT, + "candidate_answer": "" + }) + + # Calculate and persist final_summary + await self.api_client.save_final_summary(self.analysis_evaluations) + + # Persist the complete conversational transcript + await self.api_client.save_interview_metadata(self.transcript_log) + + await self.speak(CLOSING_TEXT) + self.current_interaction_state = "closing" + + # ──────────────────────────── TTS ──────────────────────────── + async def speak(self, text: str): - """Synthesizes text and streams it to the WebSocket.""" + """Synthesizes text via TTS and streams audio to the WebSocket.""" + logger.info("AI speaking", extra={"text": text[:80]}) self.current_interaction_state = "speaking" from src.screening_pipeline.audio_websocket import speak_to_attendee - + async for chunk in self.tts_client.synthesize(text): - # If candidate barged in, we would break this loop if not self.is_active: break await speak_to_attendee(self.websocket, chunk) - - async def cleanup(self): - """Teardown connections.""" - self.is_active = False - await self.stt_client.close() diff --git a/services/ai-core-services/src/screening_pipeline/prompts.py b/services/ai-core-services/src/screening_pipeline/prompts.py new file mode 100644 index 0000000..8c3bb8e --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/prompts.py @@ -0,0 +1,54 @@ +""" +LLM Prompt Templates for the AI Screening Pipeline. +Source of truth: docs/architecture/AI_PROCESSING.md (Section 5.3) +""" + +INTENT_ROUTER_SYSTEM = ( + "You are an AI Interview Intent Router. " + "Your job is to read the candidate's speech and classify it into one of four intents:\n" + "- ANSWERING: The candidate is attempting to answer the technical question.\n" + "- CLARIFICATION: The candidate is asking you to repeat, clarify, or rephrase the question.\n" + "- SMALL_TALK: The candidate is making small talk, apologizing for a delay, asking for a moment to think, or responding to a greeting.\n" + "- SKIP: The candidate explicitly states they do not know the answer and want to move on.\n\n" + "Respond in JSON format: {\"intent\": \"\", \"response\": \"\"}" +) + +ANSWER_EVALUATION_SYSTEM = ( + "STRICTNESS DEFINITIONS:\n" + "- \"aware\": Accept the answer as-is. Any reasonable attempt at a response is sufficient. Do not penalize for missing keywords.\n" + "- \"partial_depth\": Accept if the answer covers some of the expected keywords with a basic explanation. Partial understanding is acceptable.\n" + "- \"full_depth\": Accept only if the answer covers most of the expected keywords with a clear and accurate explanation. Vague or incomplete answers are not sufficient.\n\n" + "SCORING RULES:\n" + "- Score 0\u201310. Your score MUST reflect BOTH keyword coverage AND the EVALUATION STRICTNESS LEVEL above.\n" + "- If STRICTNESS LEVEL is \"aware\": Score generously (7-10) if they show basic understanding, even if missing keywords.\n" + "- If STRICTNESS LEVEL is \"partial_depth\": Score 7-10 only if they hit some keywords and explain the basic concept.\n" + "- If STRICTNESS LEVEL is \"full_depth\": Score strictly. Score 7-10 ONLY if they hit most keywords with a clear, accurate explanation.\n" + "- Score 5\u20136: Candidate fell short of the required strictness level or missed key concepts.\n" + "- Score 0\u20134: Wrong, confused, or vague with no real understanding shown.\n" + "- Do NOT penalize for informal phrasing if the technical concept is correct.\n\n" + "DECISION:\n" + "- \"NEXT_QUESTION\" if score >= 6 AND coverage_percent >= 50 (candidate understood it well enough for screening).\n" + "- \"ASK_FOLLOW_UP\" if score < 6 OR coverage_percent < 50 (answer was too shallow or missed key concepts).\n" + "- \"REPEAT_QUESTION\" if the candidate asked you to repeat the question, or if their response was completely unrelated to the interview (e.g. \"I can't hear you\", \"Hold on a second\").\n\n" + "Return STRICT JSON only. No markdown:\n" + "{\n" + " \"score\": <0-10>,\n" + " \"coverage_percent\": <0-100>,\n" + " \"keywords_found\": [\"...\"],\n" + " \"keywords_missing\": [\"...\"],\n" + " \"is_sufficient\": ,\n" + " \"decision\": \"NEXT_QUESTION | ASK_FOLLOW_UP | REPEAT_QUESTION\",\n" + " \"feedback\": \"2-3 sentences: what was good, what was missing, pass/fail on this topic for screening\",\n" + " \"suggested_follow_up\": \"If decision is ASK_FOLLOW_UP and this is NOT a follow-up evaluation itself, write a specific, conversational follow-up question here to probe what they missed based on the missing keywords. If REPEAT_QUESTION, omit this field.\"\n" + "}" +) + +# Greeting and closing messages +GREETING_TEXT = "Hi! I am your interviewer for today's interview. Let's start with some technical questions." +CLOSING_TEXT = "Thank you for your time today. Our HR team will be in touch shortly." + +# Follow-up limit per question +MAX_FOLLOW_UPS_PER_QUESTION = 1 + +# Recommendation threshold (AI_PROCESSING.md Section 5.4) +RECOMMENDATION_THRESHOLD = 6.0 diff --git a/services/ai-core-services/src/screening_pipeline/session_api.py b/services/ai-core-services/src/screening_pipeline/session_api.py new file mode 100644 index 0000000..c67c0ee --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/session_api.py @@ -0,0 +1,117 @@ +""" +Core API Client for persisting interview data. +Handles all HTTP communication with the core-api service. +""" +import json +import httpx +from typing import Dict, Any, List +from src.core.config import settings +from src.core.logger import logger +from src.screening_pipeline.prompts import RECOMMENDATION_THRESHOLD + + +class SessionApiClient: + """Handles all core-api HTTP calls for a specific interview session.""" + + def __init__(self, session_id: str): + self.session_id = session_id + self.base_url = settings.core_api_url.rstrip("/") + + async def save_transcript(self, qa_entry: Dict[str, Any]): + """Appends a single Q&A entry to the question_answer column.""" + logger.info("Saving transcript to core-api", extra={ + "session_id": self.session_id, + "question_id": qa_entry.get("question_id") + }) + logger.debug("Transcript payload", extra={"payload": json.dumps(qa_entry, indent=2)}) + + try: + url = f"{self.base_url}/api/v1/interview-sessions/{self.session_id}/transcript" + async with httpx.AsyncClient(timeout=5.0) as client: + resp = await client.post(url, json=qa_entry) + if resp.status_code not in (200, 201, 204): + logger.error("Failed to save transcript", extra={"status": resp.status_code}) + except Exception as err: + logger.error("Error saving transcript to core-api", extra={"error": str(err)}) + + async def save_evaluation(self, evaluation: Dict[str, Any]): + """Appends a single evaluation block to analysis_result.evaluations.""" + logger.info("Saving evaluation to core-api", extra={ + "session_id": self.session_id, + "question_id": evaluation.get("question_id"), + "score": evaluation.get("score"), + "decision": evaluation.get("decision") + }) + logger.debug("Evaluation payload", extra={"payload": json.dumps(evaluation, indent=2)}) + + try: + url = f"{self.base_url}/api/v1/interview-sessions/{self.session_id}/evaluation" + async with httpx.AsyncClient(timeout=5.0) as client: + resp = await client.post(url, json=evaluation) + if resp.status_code not in (200, 201, 204): + logger.error("Failed to save evaluation", extra={"status": resp.status_code}) + except Exception as err: + logger.error("Error saving evaluation to core-api", extra={"error": str(err)}) + + async def save_final_summary(self, evaluations: List[Dict[str, Any]]): + """Calculates the final_summary from all evaluations and sends it to core-api.""" + total_questions = len(evaluations) + if total_questions == 0: + return + + # Topic Score Resolution (AI_PROCESSING.md Section 5.4) + total_score = 0.0 + for ev in evaluations: + primary_score = ev.get("score", 0) + follow_ups = ev.get("follow_ups", []) + if follow_ups and follow_ups[0].get("score") is not None: + topic_score = (primary_score + follow_ups[0]["score"]) / 2 + else: + topic_score = primary_score + total_score += topic_score + + max_possible_score = total_questions * 10 + overall_score = round((total_score / max_possible_score) * 10, 1) + + if overall_score >= RECOMMENDATION_THRESHOLD: + final_recommendation = "shortlist_for_l1" + else: + final_recommendation = "reject" + + final_summary = { + "total_score": total_score, + "max_possible_score": max_possible_score, + "overall_score": overall_score, + "final_recommendation": final_recommendation + } + + logger.info("Saving final summary to core-api", extra={ + "session_id": self.session_id, + "overall_score": overall_score, + "recommendation": final_recommendation + }) + + try: + url = f"{self.base_url}/api/v1/interview-sessions/{self.session_id}/evaluation/summary" + async with httpx.AsyncClient(timeout=5.0) as client: + resp = await client.post(url, json=final_summary) + if resp.status_code not in (200, 201, 204): + logger.error("Failed to save final summary", extra={"status": resp.status_code}) + except Exception as err: + logger.error("Error saving final summary to core-api", extra={"error": str(err)}) + + async def save_interview_metadata(self, transcript_log: List[Dict[str, Any]]): + """Saves the full conversational transcript (greetings, small talk, QA) to interview_metadata.""" + logger.info("Saving full interview metadata to core-api", extra={ + "session_id": self.session_id, + "total_interactions": len(transcript_log) + }) + + try: + url = f"{self.base_url}/api/v1/interview-sessions/{self.session_id}/metadata" + async with httpx.AsyncClient(timeout=5.0) as client: + resp = await client.post(url, json={"interview_metadata": transcript_log}) + if resp.status_code not in (200, 201, 204): + logger.error("Failed to save interview metadata", extra={"status": resp.status_code}) + except Exception as err: + logger.error("Error saving interview metadata to core-api", extra={"error": str(err)}) diff --git a/services/ai-core-services/src/screening_pipeline/stt_client.py b/services/ai-core-services/src/screening_pipeline/stt_client.py index eb59660..1d0f71c 100644 --- a/services/ai-core-services/src/screening_pipeline/stt_client.py +++ b/services/ai-core-services/src/screening_pipeline/stt_client.py @@ -1,27 +1,127 @@ import asyncio +import io +import wave +import httpx +import webrtcvad +import struct from typing import Callable from src.core.logger import logger -class DeepgramSTTClient: - """Streams PCM audio to Deepgram via WebSocket and emits transcription events.""" +class WhisperCloudSTTClient: + """ + Accumulates 24kHz PCM audio from the WebSocket, uses WebRTC VAD to detect + when the candidate stops speaking, and uploads the chunk to Whisper API. + """ - def __init__(self, api_key: str, on_transcript: Callable[[str], None]): + def __init__(self, api_url: str, api_key: str, on_transcript: Callable[[str], None]): + self.api_url = api_url or "https://api.groq.com/openai/v1/audio/transcriptions" self.api_key = api_key self.on_transcript = on_transcript - self._is_connected = False - + + self.vad = webrtcvad.Vad(3) # Aggressiveness 3 (highest) + self.audio_buffer = bytearray() + self.is_speaking = False + self.silence_frames = 0 + self.MAX_SILENCE_FRAMES = 50 # ~1.5 seconds of silence before chunking (assuming 30ms frames) + self.is_connected = True + + self.process_task = None + async def connect(self): - """Establish WebSocket connection to STT namespace.""" - logger.info("Initializing STT connection") - self._is_connected = True - + """Initialize HTTP client for Whisper.""" + logger.info("Initializing Whisper Cloud STT Client") + self.is_connected = True + + def _downsample_24k_to_16k(self, pcm_bytes: bytes) -> bytes: + """ + WebRTC VAD only supports 8kHz, 16kHz, 32kHz, 48kHz. + We drop every 3rd sample to convert 24kHz to 16kHz. + """ + samples = struct.unpack(f"<{len(pcm_bytes)//2}h", pcm_bytes) + downsampled = [samples[i] for i in range(len(samples)) if i % 3 != 2] + return struct.pack(f"<{len(downsampled)}h", *downsampled) + async def send_audio(self, pcm_bytes: bytes): - """Send raw PCM chunk to STT.""" - if not self._is_connected: + """Receive raw 24kHz PCM audio from websocket.""" + if not self.is_connected: return - pass - + + # We process in 30ms chunks. 24000Hz * 16-bit (2 bytes) * 0.03 = 1440 bytes + chunk_size = 1440 + + # Buffer incoming bytes + self.audio_buffer.extend(pcm_bytes) + + while len(self.audio_buffer) >= chunk_size: + frame = self.audio_buffer[:chunk_size] + self.audio_buffer = self.audio_buffer[chunk_size:] + + # VAD check requires 16kHz (30ms = 960 bytes) + frame_16k = self._downsample_24k_to_16k(frame) + + try: + is_speech = self.vad.is_speech(frame_16k, 16000) + except Exception as e: + is_speech = False + + if is_speech: + self.is_speaking = True + self.silence_frames = 0 + elif self.is_speaking: + self.silence_frames += 1 + + # If they stopped speaking for MAX_SILENCE_FRAMES, process the chunk! + if self.is_speaking and self.silence_frames > self.MAX_SILENCE_FRAMES: + asyncio.create_task(self._process_chunk_and_reset()) + + async def _process_chunk_and_reset(self): + """Takes the current buffer, creates a WAV, and sends to Whisper.""" + self.is_speaking = False + self.silence_frames = 0 + + # Deep copy buffer to clear it for the next sentence + audio_chunk = bytes(self.audio_buffer) + self.audio_buffer = bytearray() + + if len(audio_chunk) < 24000: # Ignore clips less than 0.5s + return + + wav_io = io.BytesIO() + with wave.open(wav_io, 'wb') as wav_file: + wav_file.setnchannels(1) + wav_file.setsampwidth(2) + wav_file.setframerate(24000) + wav_file.writeframes(audio_chunk) + + wav_bytes = wav_io.getvalue() + + try: + async with httpx.AsyncClient(timeout=10.0) as client: + files = { + 'file': ('audio.wav', wav_bytes, 'audio/wav') + } + data = { + 'model': 'distil-whisper-large-v3-en', # Groq's model name, update if using another provider + 'response_format': 'json' + } + headers = { + 'Authorization': f'Bearer {self.api_key}' + } + + logger.debug("Sending audio chunk to Whisper API...") + resp = await client.post(self.api_url, files=files, data=data, headers=headers) + + if resp.status_code == 200: + transcript = resp.json().get('text', '').strip() + if transcript: + logger.debug(f"Whisper Transcript: {transcript}") + self.on_transcript(transcript) + else: + logger.error(f"Whisper API error: {resp.text}") + except Exception as e: + logger.error(f"Failed to transcribe chunk: {e}") + async def close(self): """Gracefully close STT connection.""" - self._is_connected = False - logger.info("Closed STT connection") + self.is_connected = False + logger.info("Closed Whisper STT client") diff --git a/services/ai-core-services/src/screening_pipeline/tts_client.py b/services/ai-core-services/src/screening_pipeline/tts_client.py index 1474e55..40ce3c1 100644 --- a/services/ai-core-services/src/screening_pipeline/tts_client.py +++ b/services/ai-core-services/src/screening_pipeline/tts_client.py @@ -1,20 +1,60 @@ import asyncio +import httpx from typing import AsyncGenerator from src.core.logger import logger -class TTSClient: - """Generates PCM audio from text using a TTS provider.""" +class KokoroCloudTTSClient: + """Generates PCM audio from text using the Kokoro Cloud API.""" - def __init__(self, api_key: str): + def __init__(self, api_url: str, api_key: str): + self.api_url = api_url or "https://api.replicate.com/v1/predictions" self.api_key = api_key async def synthesize(self, text: str) -> AsyncGenerator[bytes, None]: """ Yields raw PCM chunks (24kHz) for the given text. """ - logger.debug(f"Synthesizing TTS: {text[:30]}...") + if not self.api_key: + logger.warning("KOKORO_API_KEY is not set, defaulting to silence stub.") + yield b'\x00' * 2400 + await asyncio.sleep(0.05) + return + + logger.debug(f"Synthesizing TTS via Kokoro: {text[:30]}...") + + headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + + # NOTE: This payload format is an example (e.g. Replicate format). + # Update this to match your exact Kokoro Cloud API provider. + payload = { + "input": { + "text": text, + "voice": "af_bella" # Default kokoro voice + } + } - # Stub: just yield a dummy 2400-byte frame of silence to simulate output - dummy_chunk = b'\x00' * 2400 - yield dummy_chunk - await asyncio.sleep(0.05) + try: + # For APIs that stream the audio back immediately (like standard TTS APIs) + async with httpx.AsyncClient() as client: + # If the API returns a direct stream of bytes (WAV/PCM) + async with client.stream("POST", self.api_url, json=payload, headers=headers, timeout=15.0) as response: + response.raise_for_status() + + # Note: If Kokoro returns a WAV file with headers, + # the first few chunks will contain the WAV header. + # A robust implementation would strip the 44-byte WAV header here. + is_first_chunk = True + async for chunk in response.aiter_bytes(chunk_size=2400): + if is_first_chunk and chunk.startswith(b'RIFF'): + # Strip 44 byte WAV header safely + chunk = chunk[44:] + is_first_chunk = False + yield chunk + except Exception as e: + logger.error(f"Kokoro TTS synthesis failed: {e}") + # Fallback to silence if API fails + yield b'\x00' * 2400 + await asyncio.sleep(0.05) diff --git a/services/ai-core-services/src/screening_pipeline/webhook_handler.py b/services/ai-core-services/src/screening_pipeline/webhook_handler.py index 7baef83..3caee12 100644 --- a/services/ai-core-services/src/screening_pipeline/webhook_handler.py +++ b/services/ai-core-services/src/screening_pipeline/webhook_handler.py @@ -5,7 +5,7 @@ from src.core.config import settings from src.meeting_bot.repository import interview_session_repo -router = APIRouter(tags=["Attendee Webhooks"]) +router = APIRouter(prefix="/screening", tags=["Attendee Webhooks"]) async def update_session_status(session_id: str, new_status: str): """Make an internal API call to core-api to update session status.""" diff --git a/services/ai-core-services/test_websocket.py b/services/ai-core-services/test_websocket.py deleted file mode 100644 index 0b4b4b1..0000000 --- a/services/ai-core-services/test_websocket.py +++ /dev/null @@ -1,49 +0,0 @@ -import asyncio -import websockets -import json -import base64 - -async def test_bot_connection(): - uri = "ws://localhost:8002/attendee-websocket" - - print(f"Connecting to {uri}...") - async with websockets.connect(uri) as websocket: - print("Connected!") - - # 1. Simulate Attendee sending the bot.joined event - join_payload = { - "trigger": "bot.joined", - "data": { - "bot_id": "test_bot_123" - } - } - await websocket.send(json.dumps(join_payload)) - print("Sent bot.joined event.") - - # 2. Listen for the Bot's Greeting (TTS output) - while True: - try: - msg = await asyncio.wait_for(websocket.recv(), timeout=2.0) - data = json.loads(msg) - if data.get("trigger") == "realtime_audio.bot_output": - print("Received PCM audio chunk from Bot!") - # In a real test, you'd decode this base64 and play it - # chunk = base64.b64decode(data["data"]["chunk"]) - except asyncio.TimeoutError: - print("No more audio received. Bot is listening...") - break - - # 3. Simulate Candidate speaking (sending dummy PCM audio) - print("Simulating candidate speech...") - dummy_pcm = b'\x00' * 2400 - speech_payload = { - "trigger": "realtime_audio.mixed", - "data": { - "chunk": base64.b64encode(dummy_pcm).decode('utf-8') - } - } - await websocket.send(json.dumps(speech_payload)) - print("Sent mock candidate audio.") - -if __name__ == "__main__": - asyncio.run(test_bot_connection()) From 125ad93d1782ccf20e0b81f89c1f8b57d5e5a86d Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Tue, 1 Sep 2026 17:37:01 +0530 Subject: [PATCH 5/8] enhance STT audio processing --- docker-compose.yml | 2 + services/ai-core-services/Dockerfile | 1 + services/ai-core-services/pyproject.toml | 3 + services/ai-core-services/src/main.py | 15 ++- .../src/meeting_bot/attendee.py | 2 + .../src/meeting_bot/client.py | 29 ++++- .../src/meeting_bot/schemas.py | 3 + .../src/screening_pipeline/audio_websocket.py | 69 ++++++++-- .../src/screening_pipeline/orchestrator.py | 35 +++-- .../src/screening_pipeline/session_api.py | 4 +- .../src/screening_pipeline/stt_client.py | 48 +++++-- .../src/screening_pipeline/tts_client.py | 121 +++++++++++------- 12 files changed, 243 insertions(+), 89 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2c78027..3c5571e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -89,6 +89,7 @@ services: volumes: - rapidocr_models:/usr/local/lib/python3.11/site-packages/rapidocr/models - hf_cache:/root/.cache/huggingface + - kokoro_models:/app/.models depends_on: db: condition: service_healthy @@ -114,3 +115,4 @@ volumes: miniodata: rapidocr_models: hf_cache: + kokoro_models: diff --git a/services/ai-core-services/Dockerfile b/services/ai-core-services/Dockerfile index 7853c9b..b882ee9 100644 --- a/services/ai-core-services/Dockerfile +++ b/services/ai-core-services/Dockerfile @@ -11,6 +11,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ libgl1 \ libglib2.0-0 \ + libsndfile1 \ && rm -rf /var/lib/apt/lists/* # Copy pyproject.toml and README diff --git a/services/ai-core-services/pyproject.toml b/services/ai-core-services/pyproject.toml index 8393117..7c8a13a 100644 --- a/services/ai-core-services/pyproject.toml +++ b/services/ai-core-services/pyproject.toml @@ -19,6 +19,9 @@ dependencies = [ "docling>=2.15.0", "boto3>=1.34.0", "webrtcvad>=2.0.10", + "kokoro-onnx>=0.1.6", + "soundfile>=0.12.1", + "numpy>=1.26.0", ] [build-system] diff --git a/services/ai-core-services/src/main.py b/services/ai-core-services/src/main.py index d2231dc..7a33648 100644 --- a/services/ai-core-services/src/main.py +++ b/services/ai-core-services/src/main.py @@ -8,10 +8,23 @@ from src.screening_pipeline.webhook_handler import router as webhook_router from src.screening_pipeline.audio_websocket import router as websocket_router +from contextlib import asynccontextmanager +from src.screening_pipeline.tts_client import LocalKokoroTTSClient + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Pre-download models on startup if they don't exist in the volume + try: + await LocalKokoroTTSClient()._ensure_models() + except Exception as e: + print(f"Failed to pre-download Kokoro models: {e}") + yield + app = FastAPI( title="EZScreen Service", description="Unified AI Microservice hosting Parsing, Matching, Screening, Attendee Bot, and Interview Analysis modules", - version="1.0.0" + version="1.0.0", + lifespan=lifespan ) app.add_middleware( diff --git a/services/ai-core-services/src/meeting_bot/attendee.py b/services/ai-core-services/src/meeting_bot/attendee.py index 372ae6c..40d428c 100644 --- a/services/ai-core-services/src/meeting_bot/attendee.py +++ b/services/ai-core-services/src/meeting_bot/attendee.py @@ -115,6 +115,8 @@ async def get_bot(self, bot_id: str) -> AttendeeBotStatusResponse: except Exception as err: logger.warning("Could not fetch bot details from Attendee API", extra={"bot_id": bot_id, "error": str(err)}) + return fallback_response + async def leave_bot(self, bot_id: str) -> LeaveBotResponse: """Instruct the bot to gracefully leave the meeting.""" if not self.api_key: diff --git a/services/ai-core-services/src/meeting_bot/client.py b/services/ai-core-services/src/meeting_bot/client.py index 902ee58..ec692dc 100644 --- a/services/ai-core-services/src/meeting_bot/client.py +++ b/services/ai-core-services/src/meeting_bot/client.py @@ -1,4 +1,6 @@ -from datetime import datetime, timezone +from fastapi import HTTPException +from datetime import datetime, timezone, timedelta +from src.core.logger import logger from src.core.config import settings from src.meeting_bot.repository import interview_session_repo from src.meeting_bot.attendee import attendee_client @@ -24,6 +26,23 @@ async def dispatch_bot(self, request: DispatchBotRequest) -> DispatchBotResponse scheduled_at = None if session_detail: scheduled_at = session_detail.scheduled_at + if scheduled_at: + try: + # The calendar saves IST but marks it as UTC. + # We subtract 5:30 to get the TRUE UTC time for Attendee.dev + dt = datetime.fromisoformat(scheduled_at.replace("Z", "+00:00")) + dt = dt - timedelta(hours=5, minutes=30) + + # If it's already past the start time, block it and return an error to the user + if dt < datetime.now(timezone.utc): + raise HTTPException(status_code=400, detail="Cannot dispatch bot. The scheduled interview time is in the past.") + else: + scheduled_at = dt.isoformat() + except HTTPException: + raise # Re-raise the 400 error so Postman sees it + except Exception as e: + logger.warning(f"Failed to adjust scheduled_at for IST: {e}") + if not meeting_url and session_detail.comment: meeting_url = session_detail.comment @@ -35,10 +54,16 @@ async def dispatch_bot(self, request: DispatchBotRequest) -> DispatchBotResponse meeting_url=meeting_url, bot_name="ezscreener", join_at=scheduled_at, + transcription_settings={ + "meeting_closed_captions": {} + }, websocket_settings=AttendeeWebsocketSettings( audio=AttendeeAudioSettings( url=f"{settings.websocket_url.rstrip('/')}/{request.interview_session_id}", - sample_rate=24000 + sample_rate=24000, + receive_audio=True, + listen=True, + events=["realtime_audio.mixed"] ) ) ) diff --git a/services/ai-core-services/src/meeting_bot/schemas.py b/services/ai-core-services/src/meeting_bot/schemas.py index 6dae324..2b28c27 100644 --- a/services/ai-core-services/src/meeting_bot/schemas.py +++ b/services/ai-core-services/src/meeting_bot/schemas.py @@ -5,6 +5,9 @@ class AttendeeAudioSettings(BaseModel): url: str sample_rate: int = 24000 + events: Optional[List[str]] = None + receive_audio: Optional[bool] = None + listen: Optional[bool] = None class AttendeeWebsocketSettings(BaseModel): diff --git a/services/ai-core-services/src/screening_pipeline/audio_websocket.py b/services/ai-core-services/src/screening_pipeline/audio_websocket.py index d99fecf..edcfb2f 100644 --- a/services/ai-core-services/src/screening_pipeline/audio_websocket.py +++ b/services/ai-core-services/src/screening_pipeline/audio_websocket.py @@ -7,6 +7,7 @@ # We will manage active sessions here active_sessions: Dict[str, Any] = {} +seen_triggers = set() async def speak_to_attendee(websocket: WebSocket, pcm_bytes: bytes): """Utility to chunk and send PCM audio to Attendee.""" @@ -22,40 +23,80 @@ async def speak_to_attendee(websocket: WebSocket, pcm_bytes: bytes): } }) -@router.websocket("/attendee-websocket/{bot_id}") -async def attendee_audio_ws(websocket: WebSocket, bot_id: str): +@router.websocket("/attendee-websocket/{session_id}") +async def attendee_audio_ws(websocket: WebSocket, session_id: str): """ WebSocket endpoint for bidirectional audio streaming. Attendee will connect to this URL. """ await websocket.accept() - logger.info("Attendee connected to WebSocket", extra={"bot_id": bot_id}) + logger.info("Attendee connected to WebSocket", extra={"session_id": session_id}) from src.screening_pipeline.orchestrator import InterviewOrchestrator - orchestrator = InterviewOrchestrator(bot_id=bot_id, websocket=websocket) - active_sessions[bot_id] = orchestrator + orchestrator = InterviewOrchestrator(session_id=session_id, websocket=websocket) + active_sessions[session_id] = orchestrator - logger.info("Bot audio stream initialized", extra={"bot_id": bot_id}) + logger.info("Bot audio stream initialized", extra={"session_id": session_id}) import asyncio asyncio.create_task(orchestrator.start()) + messages_received = 0 try: while True: - message = await websocket.receive_json() - trigger = message.get("trigger") + raw_ws_message = await websocket.receive() + + if "bytes" in raw_ws_message and raw_ws_message["bytes"]: + pcm_bytes = raw_ws_message["bytes"] + if messages_received < 5: + logger.info(f"DEBUG Raw WS BINARY Frame #{messages_received}: size {len(pcm_bytes)} bytes") + messages_received += 1 + + # If it's a raw binary frame, it's almost certainly the audio stream! + # We assume 24000 sample rate for raw inbound PCM. + if session_id in active_sessions: + await orchestrator.stt_client.send_audio(pcm_bytes, 24000) + continue + + if "text" not in raw_ws_message or not raw_ws_message["text"]: + continue + + import json + try: + message = json.loads(raw_ws_message["text"]) + except json.JSONDecodeError: + continue + + # Debug log the first 5 incoming messages to inspect their exact structure + if messages_received < 5: + # Omit base64 chunk to avoid massive logs + debug_msg = {k: v for k, v in message.items() if k != "data"} + if "data" in message and isinstance(message["data"], dict): + debug_msg["data_keys"] = list(message["data"].keys()) + if "sample_rate" in message["data"]: + debug_msg["sample_rate"] = message["data"]["sample_rate"] + logger.info(f"DEBUG Raw WS TEXT Message #{messages_received}: {debug_msg}") + messages_received += 1 + + # Attendee might use "event", "type", or "trigger" depending on API version + trigger = message.get("trigger") or message.get("event") or message.get("type") data = message.get("data", {}) - if trigger == "realtime_audio.mixed": + if trigger and trigger not in seen_triggers: + seen_triggers.add(trigger) + logger.info(f"WebSocket received new trigger: {trigger}", extra={"data_keys": list(data.keys())}) + + if trigger in ["realtime_audio.mixed", "realtime_audio.user"]: # Inbound audio from candidate chunk_b64 = data.get("chunk") - if chunk_b64 and bot_id in active_sessions: + sample_rate = data.get("sample_rate", 24000) + if chunk_b64 and session_id in active_sessions: pcm_bytes = base64.b64decode(chunk_b64) - await orchestrator.stt_client.send_audio(pcm_bytes) + await orchestrator.stt_client.send_audio(pcm_bytes, sample_rate) except WebSocketDisconnect: - logger.info("Attendee WebSocket disconnected", extra={"bot_id": bot_id}) + logger.info("Attendee WebSocket disconnected", extra={"session_id": session_id}) finally: - if bot_id and bot_id in active_sessions: - orchestrator = active_sessions.pop(bot_id) + if session_id and session_id in active_sessions: + orchestrator = active_sessions.pop(session_id) await orchestrator.cleanup() diff --git a/services/ai-core-services/src/screening_pipeline/orchestrator.py b/services/ai-core-services/src/screening_pipeline/orchestrator.py index ca6b748..cbca2db 100644 --- a/services/ai-core-services/src/screening_pipeline/orchestrator.py +++ b/services/ai-core-services/src/screening_pipeline/orchestrator.py @@ -10,7 +10,7 @@ from src.core.config import settings from src.meeting_bot.repository import interview_session_repo from src.screening_pipeline.stt_client import WhisperCloudSTTClient -from src.screening_pipeline.tts_client import KokoroCloudTTSClient +from src.screening_pipeline.tts_client import LocalKokoroTTSClient from src.screening_pipeline.evaluator import AnswerEvaluator from src.screening_pipeline.session_api import SessionApiClient from src.screening_pipeline.prompts import ( @@ -27,8 +27,8 @@ class InterviewOrchestrator: Coordinates STT, Intent Router, LLM Evaluator, TTS, and Core-API persistence. """ - def __init__(self, bot_id: str, websocket): - self.bot_id = bot_id + def __init__(self, session_id: str, websocket): + self.session_id = session_id self.websocket = websocket self.session: Any = None self.questions: list = [] @@ -41,10 +41,7 @@ def __init__(self, bot_id: str, websocket): api_key=settings.whisper_api_key, on_transcript=self.handle_candidate_speech ) - self.tts_client = KokoroCloudTTSClient( - api_url=settings.kokoro_api_url, - api_key=settings.kokoro_api_key - ) + self.tts_client = LocalKokoroTTSClient() # AI modules llm_client = OllamaClient() @@ -60,11 +57,14 @@ def __init__(self, bot_id: str, websocket): async def start(self): """Initializes the interview session and speaks the greeting.""" - logger.info("Orchestrator starting", extra={"bot_id": self.bot_id}) - self.session = await interview_session_repo.get_by_bot_id(self.bot_id) + logger.info("Orchestrator starting", extra={"session_id": self.session_id}) + + # The websocket path parameter (self.session_id) actually contains the interview_session_id + # because the true bot_id isn't known at the time the websocket URL is generated. + self.session = await interview_session_repo.get_by_id(self.session_id) if not self.session: - logger.error("No session found for bot. Cannot start interview.", extra={"bot_id": self.bot_id}) + logger.error("No session found for bot. Cannot start interview.", extra={"session_id": self.session_id}) return # Initialize the API client with the real session ID @@ -97,6 +97,21 @@ def handle_candidate_speech(self, transcript: str): """Callback from STT when the candidate finishes speaking.""" if not self.is_active or self.current_interaction_state != "listening": return + + # Ignore common Whisper hallucinations caused by background noise or silence + cleaned = transcript.strip().lower() + import re + cleaned = re.sub(r'[^\w\s]', '', cleaned) + + hallucinations = { + "thank you", "thanks", "you", "okay", "ok", "yeah", + "thank you for watching", "thanks for watching", "subscribe", + "no i dont know", "okay i dont know", "i dont know" + } + + if cleaned in hallucinations or len(cleaned) < 2: + logger.info("Ignored probable Whisper hallucination or noise", extra={"transcript": transcript}) + return logger.info("Candidate speech received", extra={"transcript": transcript}) self.current_interaction_state = "evaluating" diff --git a/services/ai-core-services/src/screening_pipeline/session_api.py b/services/ai-core-services/src/screening_pipeline/session_api.py index c67c0ee..53d1187 100644 --- a/services/ai-core-services/src/screening_pipeline/session_api.py +++ b/services/ai-core-services/src/screening_pipeline/session_api.py @@ -26,7 +26,7 @@ async def save_transcript(self, qa_entry: Dict[str, Any]): logger.debug("Transcript payload", extra={"payload": json.dumps(qa_entry, indent=2)}) try: - url = f"{self.base_url}/api/v1/interview-sessions/{self.session_id}/transcript" + url = f"{self.base_url}/api/v1/interview-sessions/{self.session_id}/qa-transcript" async with httpx.AsyncClient(timeout=5.0) as client: resp = await client.post(url, json=qa_entry) if resp.status_code not in (200, 201, 204): @@ -108,7 +108,7 @@ async def save_interview_metadata(self, transcript_log: List[Dict[str, Any]]): }) try: - url = f"{self.base_url}/api/v1/interview-sessions/{self.session_id}/metadata" + url = f"{self.base_url}/api/v1/interview-sessions/{self.session_id}/transcript" async with httpx.AsyncClient(timeout=5.0) as client: resp = await client.post(url, json={"interview_metadata": transcript_log}) if resp.status_code not in (200, 201, 204): diff --git a/services/ai-core-services/src/screening_pipeline/stt_client.py b/services/ai-core-services/src/screening_pipeline/stt_client.py index 1d0f71c..d54eef9 100644 --- a/services/ai-core-services/src/screening_pipeline/stt_client.py +++ b/services/ai-core-services/src/screening_pipeline/stt_client.py @@ -20,6 +20,7 @@ def __init__(self, api_url: str, api_key: str, on_transcript: Callable[[str], No self.vad = webrtcvad.Vad(3) # Aggressiveness 3 (highest) self.audio_buffer = bytearray() + self.speech_buffer = bytearray() # Accumulates the full audio for the API self.is_speaking = False self.silence_frames = 0 self.MAX_SILENCE_FRAMES = 50 # ~1.5 seconds of silence before chunking (assuming 30ms frames) @@ -32,36 +33,58 @@ async def connect(self): logger.info("Initializing Whisper Cloud STT Client") self.is_connected = True - def _downsample_24k_to_16k(self, pcm_bytes: bytes) -> bytes: + def _downsample_to_16k(self, pcm_bytes: bytes, orig_rate: int) -> bytes: """ - WebRTC VAD only supports 8kHz, 16kHz, 32kHz, 48kHz. - We drop every 3rd sample to convert 24kHz to 16kHz. + WebRTC VAD supports 16kHz. We convert 48kHz or 24kHz down to 16kHz. + If it's already 16kHz, return as is. """ + if orig_rate == 16000: + return pcm_bytes + samples = struct.unpack(f"<{len(pcm_bytes)//2}h", pcm_bytes) - downsampled = [samples[i] for i in range(len(samples)) if i % 3 != 2] + + if orig_rate == 48000: + # 48k to 16k: keep 1 every 3 + downsampled = [samples[i] for i in range(0, len(samples), 3)] + elif orig_rate == 24000: + # 24k to 16k: drop 1 every 3 + downsampled = [samples[i] for i in range(len(samples)) if i % 3 != 2] + else: + # Fallback (unsupported rate, just return it and hope) + return pcm_bytes + return struct.pack(f"<{len(downsampled)}h", *downsampled) - async def send_audio(self, pcm_bytes: bytes): - """Receive raw 24kHz PCM audio from websocket.""" + async def send_audio(self, pcm_bytes: bytes, sample_rate: int = 24000): + """Receive raw PCM audio from websocket at the given sample rate.""" if not self.is_connected: return - # We process in 30ms chunks. 24000Hz * 16-bit (2 bytes) * 0.03 = 1440 bytes - chunk_size = 1440 + if not hasattr(self, '_logged_rate'): + logger.info(f"STT receiving audio at sample rate: {sample_rate}") + self._logged_rate = True + + # We need 30ms chunks for VAD. + try: + chunk_size = int(int(sample_rate) * 2 * 0.03) + except Exception: + chunk_size = 1440 # fallback to 24kHz # Buffer incoming bytes self.audio_buffer.extend(pcm_bytes) + self.speech_buffer.extend(pcm_bytes) while len(self.audio_buffer) >= chunk_size: frame = self.audio_buffer[:chunk_size] self.audio_buffer = self.audio_buffer[chunk_size:] - # VAD check requires 16kHz (30ms = 960 bytes) - frame_16k = self._downsample_24k_to_16k(frame) + # VAD check requires 16kHz + frame_16k = self._downsample_to_16k(frame, int(sample_rate)) try: is_speech = self.vad.is_speech(frame_16k, 16000) except Exception as e: + logger.error(f"VAD error: {e}. frame len: {len(frame_16k)}") is_speech = False if is_speech: @@ -80,7 +103,8 @@ async def _process_chunk_and_reset(self): self.silence_frames = 0 # Deep copy buffer to clear it for the next sentence - audio_chunk = bytes(self.audio_buffer) + audio_chunk = bytes(self.speech_buffer) + self.speech_buffer = bytearray() self.audio_buffer = bytearray() if len(audio_chunk) < 24000: # Ignore clips less than 0.5s @@ -101,7 +125,7 @@ async def _process_chunk_and_reset(self): 'file': ('audio.wav', wav_bytes, 'audio/wav') } data = { - 'model': 'distil-whisper-large-v3-en', # Groq's model name, update if using another provider + 'model': 'whisper-large-v3', # Groq's active model name 'response_format': 'json' } headers = { diff --git a/services/ai-core-services/src/screening_pipeline/tts_client.py b/services/ai-core-services/src/screening_pipeline/tts_client.py index 40ce3c1..2cdf5a7 100644 --- a/services/ai-core-services/src/screening_pipeline/tts_client.py +++ b/services/ai-core-services/src/screening_pipeline/tts_client.py @@ -3,58 +3,83 @@ from typing import AsyncGenerator from src.core.logger import logger -class KokoroCloudTTSClient: - """Generates PCM audio from text using the Kokoro Cloud API.""" - - def __init__(self, api_url: str, api_key: str): - self.api_url = api_url or "https://api.replicate.com/v1/predictions" - self.api_key = api_key +import os +import numpy as np +from src.core.config import settings + +class LocalKokoroTTSClient: + """Generates PCM audio using a local Kokoro-82M ONNX model.""" - async def synthesize(self, text: str) -> AsyncGenerator[bytes, None]: - """ - Yields raw PCM chunks (24kHz) for the given text. - """ - if not self.api_key: - logger.warning("KOKORO_API_KEY is not set, defaulting to silence stub.") - yield b'\x00' * 2400 - await asyncio.sleep(0.05) + def __init__(self, model_dir: str = ".models"): + self.model_dir = model_dir + self.model_path = os.path.join(model_dir, "kokoro-v1.0.onnx") + self.voices_path = os.path.join(model_dir, "voices-v1.0.bin") + self.kokoro = None + self._download_lock = asyncio.Lock() + + async def _ensure_models(self): + """Downloads the ONNX model and voices binary if they don't exist.""" + if os.path.exists(self.model_path) and os.path.exists(self.voices_path): + if self.kokoro is None: + from kokoro_onnx import Kokoro + self.kokoro = Kokoro(self.model_path, self.voices_path) return - logger.debug(f"Synthesizing TTS via Kokoro: {text[:30]}...") - - headers = { - "Authorization": f"Bearer {self.api_key}", - "Content-Type": "application/json" - } - - # NOTE: This payload format is an example (e.g. Replicate format). - # Update this to match your exact Kokoro Cloud API provider. - payload = { - "input": { - "text": text, - "voice": "af_bella" # Default kokoro voice - } - } - - try: - # For APIs that stream the audio back immediately (like standard TTS APIs) - async with httpx.AsyncClient() as client: - # If the API returns a direct stream of bytes (WAV/PCM) - async with client.stream("POST", self.api_url, json=payload, headers=headers, timeout=15.0) as response: - response.raise_for_status() + async with self._download_lock: + # Double check in case another task downloaded it while we were waiting + if os.path.exists(self.model_path) and os.path.exists(self.voices_path): + if self.kokoro is None: + from kokoro_onnx import Kokoro + self.kokoro = Kokoro(self.model_path, self.voices_path) + return + + logger.info("Downloading local Kokoro-82M model files (~80MB)... This only happens once.") + os.makedirs(self.model_dir, exist_ok=True) + + async with httpx.AsyncClient(follow_redirects=True) as client: + # Download ONNX + logger.info("Downloading kokoro-v1.0.onnx...") + resp = await client.get("https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx", timeout=300.0) + resp.raise_for_status() + with open(self.model_path, "wb") as f: + f.write(resp.content) - # Note: If Kokoro returns a WAV file with headers, - # the first few chunks will contain the WAV header. - # A robust implementation would strip the 44-byte WAV header here. - is_first_chunk = True - async for chunk in response.aiter_bytes(chunk_size=2400): - if is_first_chunk and chunk.startswith(b'RIFF'): - # Strip 44 byte WAV header safely - chunk = chunk[44:] - is_first_chunk = False - yield chunk + # Download voices + logger.info("Downloading voices-v1.0.bin...") + resp = await client.get("https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin", timeout=60.0) + resp.raise_for_status() + with open(self.voices_path, "wb") as f: + f.write(resp.content) + + logger.info("Kokoro models downloaded successfully!") + from kokoro_onnx import Kokoro + self.kokoro = Kokoro(self.model_path, self.voices_path) + + async def synthesize(self, text: str) -> AsyncGenerator[bytes, None]: + """Yields raw PCM chunks (24kHz) for the given text.""" + try: + await self._ensure_models() + + logger.debug(f"Synthesizing TTS locally: {text[:30]}...") + + loop = asyncio.get_running_loop() + samples, sample_rate = await loop.run_in_executor( + None, + lambda: self.kokoro.create(text, voice="af_bella", speed=1.0, lang="en-us") + ) + + # kokoro-onnx returns a float array in [-1.0, 1.0]. Convert to 16-bit PCM bytes + audio_int16 = (samples * 32767).astype(np.int16) + pcm_bytes = audio_int16.tobytes() + + # Yield in 2400-byte chunks + chunk_size = 2400 + for i in range(0, len(pcm_bytes), chunk_size): + yield pcm_bytes[i:i+chunk_size] + # Small sleep to yield control to the event loop + await asyncio.sleep(0.01) + except Exception as e: - logger.error(f"Kokoro TTS synthesis failed: {e}") - # Fallback to silence if API fails + logger.error(f"Local Kokoro TTS synthesis failed: {e}") yield b'\x00' * 2400 await asyncio.sleep(0.05) From 6eba47f460d33094dc4e5345bf9e3b51e9d33132 Mon Sep 17 00:00:00 2001 From: devkatevighnesh Date: Wed, 2 Sep 2026 16:36:44 +0530 Subject: [PATCH 6/8] graceful websocket disconnection handling --- services/ai-core-services/src/meeting_bot/client.py | 7 ++----- services/ai-core-services/src/meeting_bot/schemas.py | 3 --- .../src/screening_pipeline/audio_websocket.py | 4 ++++ .../ai-core-services/src/screening_pipeline/stt_client.py | 3 ++- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/services/ai-core-services/src/meeting_bot/client.py b/services/ai-core-services/src/meeting_bot/client.py index ec692dc..c41c423 100644 --- a/services/ai-core-services/src/meeting_bot/client.py +++ b/services/ai-core-services/src/meeting_bot/client.py @@ -57,13 +57,10 @@ async def dispatch_bot(self, request: DispatchBotRequest) -> DispatchBotResponse transcription_settings={ "meeting_closed_captions": {} }, - websocket_settings=AttendeeWebsocketSettings( + websocket_settings=AttendeeWebsocketSettings( audio=AttendeeAudioSettings( url=f"{settings.websocket_url.rstrip('/')}/{request.interview_session_id}", - sample_rate=24000, - receive_audio=True, - listen=True, - events=["realtime_audio.mixed"] + sample_rate=24000 ) ) ) diff --git a/services/ai-core-services/src/meeting_bot/schemas.py b/services/ai-core-services/src/meeting_bot/schemas.py index 2b28c27..6dae324 100644 --- a/services/ai-core-services/src/meeting_bot/schemas.py +++ b/services/ai-core-services/src/meeting_bot/schemas.py @@ -5,9 +5,6 @@ class AttendeeAudioSettings(BaseModel): url: str sample_rate: int = 24000 - events: Optional[List[str]] = None - receive_audio: Optional[bool] = None - listen: Optional[bool] = None class AttendeeWebsocketSettings(BaseModel): diff --git a/services/ai-core-services/src/screening_pipeline/audio_websocket.py b/services/ai-core-services/src/screening_pipeline/audio_websocket.py index edcfb2f..48fa7e0 100644 --- a/services/ai-core-services/src/screening_pipeline/audio_websocket.py +++ b/services/ai-core-services/src/screening_pipeline/audio_websocket.py @@ -46,6 +46,10 @@ async def attendee_audio_ws(websocket: WebSocket, session_id: str): while True: raw_ws_message = await websocket.receive() + if raw_ws_message.get("type") == "websocket.disconnect": + logger.info("Attendee WebSocket disconnected gracefully (ASGI disconnect)") + break + if "bytes" in raw_ws_message and raw_ws_message["bytes"]: pcm_bytes = raw_ws_message["bytes"] if messages_received < 5: diff --git a/services/ai-core-services/src/screening_pipeline/stt_client.py b/services/ai-core-services/src/screening_pipeline/stt_client.py index d54eef9..1617e75 100644 --- a/services/ai-core-services/src/screening_pipeline/stt_client.py +++ b/services/ai-core-services/src/screening_pipeline/stt_client.py @@ -126,7 +126,8 @@ async def _process_chunk_and_reset(self): } data = { 'model': 'whisper-large-v3', # Groq's active model name - 'response_format': 'json' + 'response_format': 'json', + 'language': 'en' } headers = { 'Authorization': f'Bearer {self.api_key}' From a3649583be065aa252041ea6694e8823ce6142d3 Mon Sep 17 00:00:00 2001 From: nikhilgosavi-josh Date: Mon, 31 Aug 2026 16:38:53 +0530 Subject: [PATCH 7/8] feat(ai-core-services): add skills, tests, and question generation updates --- services/ai-core-services/.agents/AGENTS.md | 42 + .../skills/python-design-patterns/SKILL.md | 85 + .../references/details.md | 353 ++ .../skills/python-project-structure/SKILL.md | 252 ++ .../skills/python-testing-patterns/SKILL.md | 278 ++ .../references/advanced-patterns.md | 411 ++ .../references/details.md | 349 ++ services/ai-core-services/.dockerignore | 12 + services/ai-core-services/.env.example | 3 + services/ai-core-services/README.md | 48 +- services/ai-core-services/pyproject.toml | 12 +- services/ai-core-services/skills-lock.json | 23 + .../src/api/v1/question_generation.py | 8 +- services/ai-core-services/src/core/config.py | 3 + .../src/job_fit_analysis/__init__.py | 19 + .../src/job_fit_analysis/matcher.py | 230 +- .../src/job_fit_analysis/prompt_builder.py | 151 + .../src/job_fit_analysis/score_calculator.py | 50 + .../ai-core-services/src/parsing/__init__.py | 35 + .../src/parsing/docling_wrapper.py | 43 +- .../src/parsing/experience_calculator.py | 70 + .../ai-core-services/src/parsing/jd_parser.py | 79 +- .../src/parsing/jd_prompt_builder.py | 43 + .../src/parsing/prompt_builder.py | 205 + .../src/parsing/resume_parser.py | 311 +- .../src/question_generation/__init__.py | 11 + .../src/question_generation/generator.py | 87 +- .../src/question_generation/match_context.py | 38 + .../src/question_generation/prompt_builder.py | 4 +- .../src/question_generation/schemas.py | 12 +- services/ai-core-services/tests/__init__.py | 0 .../ai-core-services/tests/api/__init__.py | 0 .../ai-core-services/tests/api/v1/__init__.py | 0 .../ai-core-services/tests/api/v1/conftest.py | 11 + .../tests/api/v1/test_matching.py | 74 + services/ai-core-services/tests/conftest.py | 1 + .../tests/job_fit_analysis/__init__.py | 0 .../job_fit_analysis/test_score_calculator.py | 87 + .../tests/parsing/__init__.py | 0 .../parsing/test_experience_calculator.py | 51 + .../tests/parsing/test_schemas.py | 29 + services/ai-core-services/uv.lock | 3547 +++++++++++++++++ 42 files changed, 6431 insertions(+), 636 deletions(-) create mode 100644 services/ai-core-services/.agents/AGENTS.md create mode 100644 services/ai-core-services/.agents/skills/python-design-patterns/SKILL.md create mode 100644 services/ai-core-services/.agents/skills/python-design-patterns/references/details.md create mode 100644 services/ai-core-services/.agents/skills/python-project-structure/SKILL.md create mode 100644 services/ai-core-services/.agents/skills/python-testing-patterns/SKILL.md create mode 100644 services/ai-core-services/.agents/skills/python-testing-patterns/references/advanced-patterns.md create mode 100644 services/ai-core-services/.agents/skills/python-testing-patterns/references/details.md create mode 100644 services/ai-core-services/.dockerignore create mode 100644 services/ai-core-services/skills-lock.json create mode 100644 services/ai-core-services/src/job_fit_analysis/__init__.py create mode 100644 services/ai-core-services/src/job_fit_analysis/prompt_builder.py create mode 100644 services/ai-core-services/src/job_fit_analysis/score_calculator.py create mode 100644 services/ai-core-services/src/parsing/__init__.py create mode 100644 services/ai-core-services/src/parsing/experience_calculator.py create mode 100644 services/ai-core-services/src/parsing/jd_prompt_builder.py create mode 100644 services/ai-core-services/src/parsing/prompt_builder.py create mode 100644 services/ai-core-services/src/question_generation/__init__.py create mode 100644 services/ai-core-services/src/question_generation/match_context.py create mode 100644 services/ai-core-services/tests/__init__.py create mode 100644 services/ai-core-services/tests/api/__init__.py create mode 100644 services/ai-core-services/tests/api/v1/__init__.py create mode 100644 services/ai-core-services/tests/api/v1/conftest.py create mode 100644 services/ai-core-services/tests/api/v1/test_matching.py create mode 100644 services/ai-core-services/tests/conftest.py create mode 100644 services/ai-core-services/tests/job_fit_analysis/__init__.py create mode 100644 services/ai-core-services/tests/job_fit_analysis/test_score_calculator.py create mode 100644 services/ai-core-services/tests/parsing/__init__.py create mode 100644 services/ai-core-services/tests/parsing/test_experience_calculator.py create mode 100644 services/ai-core-services/tests/parsing/test_schemas.py create mode 100644 services/ai-core-services/uv.lock diff --git a/services/ai-core-services/.agents/AGENTS.md b/services/ai-core-services/.agents/AGENTS.md new file mode 100644 index 0000000..f8211ce --- /dev/null +++ b/services/ai-core-services/.agents/AGENTS.md @@ -0,0 +1,42 @@ +# ai-core-services Agent Guide + +Python microservice (FastAPI, port 8002). Internal AI capabilities for EZScreen. + +## Skills (read before major changes) + +| Skill | Path | Use when | +|-------|------|----------| +| Project structure | `.agents/skills/python-project-structure/SKILL.md` | New modules, `__all__`, directory layout | +| Design patterns | `.agents/skills/python-design-patterns/SKILL.md` | Refactoring, layering, DI, SRP | +| Testing | `.agents/skills/python-testing-patterns/SKILL.md` | pytest, fixtures, mocking | + +## Architecture + +``` +src/ +├── main.py # FastAPI app, router registration +├── core/ # config, logger, storage +├── common/ # llm_utils and shared helpers +├── llm/ # OllamaClient wrapper +├── api/v1/ # HTTP controllers (/internal/v1/*) +├── parsing/ # JD & resume parsing +├── job_fit_analysis/ # Resume-JD matching & scoring +├── question_generation/ # Interview question generation +├── meeting_bot/ # Attendee.dev integration +├── screening_pipeline/ # STT/TTS (planned) +└── interview_analysis/ # Post-call evaluation (planned) +``` + +## Layering rules + +1. **API layer** (`api/v1/`) — request/response only; no LLM prompts or business formulas +2. **Domain layer** — orchestration (`matcher.py`, parsers); accepts injected clients +3. **Pure logic** — `score_calculator.py`, schema validation; no I/O, fully unit-testable +4. **Prompts** — `prompt_builder.py` per domain; large templates stay out of orchestrators + +## Testing strategy + +- `tests/job_fit_analysis/test_score_calculator.py` — deterministic scoring math +- `tests/parsing/test_schemas.py` — Pydantic model validation +- `tests/api/v1/` — route tests with mocked domain services +- Never hit real Ollama/MinIO/Attendee in unit tests diff --git a/services/ai-core-services/.agents/skills/python-design-patterns/SKILL.md b/services/ai-core-services/.agents/skills/python-design-patterns/SKILL.md new file mode 100644 index 0000000..93e8a7d --- /dev/null +++ b/services/ai-core-services/.agents/skills/python-design-patterns/SKILL.md @@ -0,0 +1,85 @@ +--- +name: python-design-patterns +description: Python design patterns including KISS, Separation of Concerns, Single Responsibility, and composition over inheritance. Use this skill when designing a new service or component from scratch and choosing how to layer responsibilities, when refactoring a God class or monolithic function that has grown too large, when deciding whether to add a new abstraction or live with duplication, when evaluating a pull request for structural issues like tight coupling or leaking internal types, when choosing between inheritance and composition for a new class hierarchy, or when a codebase is becoming hard to test because of entangled I/O and business logic. +--- + +# Python Design Patterns + +Write maintainable Python code using fundamental design principles. These patterns help you build systems that are easy to understand, test, and modify. + +## When to Use This Skill + +- Designing new components or services +- Refactoring complex or tangled code +- Deciding whether to create an abstraction +- Choosing between inheritance and composition +- Evaluating code complexity and coupling +- Planning modular architectures + +## Core Concepts + +### 1. KISS (Keep It Simple) + +Choose the simplest solution that works. Complexity must be justified by concrete requirements. + +### 2. Single Responsibility (SRP) + +Each unit should have one reason to change. Separate concerns into focused components. + +### 3. Composition Over Inheritance + +Build behavior by combining objects, not extending classes. + +### 4. Rule of Three + +Wait until you have three instances before abstracting. Duplication is often better than premature abstraction. + +## Quick Start + +```python +# Simple beats clever +# Instead of a factory/registry pattern: +FORMATTERS = {"json": JsonFormatter, "csv": CsvFormatter} + +def get_formatter(name: str) -> Formatter: + return FORMATTERS[name]() +``` + +## Detailed patterns and worked examples + +Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient. + +## Best Practices Summary + +1. **Keep it simple** - Choose the simplest solution that works +2. **Single responsibility** - Each unit has one reason to change +3. **Separate concerns** - Distinct layers with clear purposes +4. **Compose, don't inherit** - Combine objects for flexibility +5. **Rule of three** - Wait before abstracting +6. **Keep functions small** - 20-50 lines (varies by complexity), one purpose +7. **Inject dependencies** - Constructor injection for testability +8. **Delete before abstracting** - Remove dead code, then consider patterns +9. **Test each layer** - Isolated tests for each concern +10. **Explicit over clever** - Readable code beats elegant code + +## Troubleshooting + +**A class is growing and seems to have multiple responsibilities, but splitting it feels wrong.** +Apply the "reason to change" test: list every change that could require editing this class. If the list has items from different domains (e.g., HTTP parsing AND business rules AND formatting), split it. If all changes stem from the same domain concern, the class may be appropriately sized. + +**Injecting all dependencies through the constructor is producing constructors with 7+ parameters.** +This is a sign of too many responsibilities in one class, not a problem with dependency injection. Split the class into smaller units first, then each constructor naturally becomes smaller. + +**Composition is producing deeply nested wrapper objects that are hard to trace.** +Keep the composition shallow (2-3 levels). If wrapping is the only mechanism, consider whether a Protocol-based approach or simple function composition would be cleaner than a chain of decorator objects. + +**The rule of three says not to abstract yet, but the duplication is causing bugs when one copy is updated but not the other.** +Duplication that diverges in dangerous ways should be abstracted sooner. The rule of three is a heuristic, not a law. If the copies are already diverging incorrectly, extract immediately and add a test that exercises the shared behavior. + +**A service layer is importing from the API layer, breaking the dependency direction.** +This is a layering violation. The service layer must not import from handlers. Introduce a shared types/models layer that both can import from, keeping the dependency arrow pointing downward (API → Service → Repository). + +## Related Skills + +- [python-testing-patterns](../python-testing-patterns/SKILL.md) — Test each layer in isolation using the dependency injection structure established here +- [python-project-structure](../python-project-structure/SKILL.md) — Organize modules and directory layout so layer boundaries are explicit from the start diff --git a/services/ai-core-services/.agents/skills/python-design-patterns/references/details.md b/services/ai-core-services/.agents/skills/python-design-patterns/references/details.md new file mode 100644 index 0000000..707d9c4 --- /dev/null +++ b/services/ai-core-services/.agents/skills/python-design-patterns/references/details.md @@ -0,0 +1,353 @@ +# python-design-patterns — detailed patterns and worked examples + +## Fundamental Patterns + +### Pattern 1: KISS - Keep It Simple + +Before adding complexity, ask: does a simpler solution work? + +```python +# Over-engineered: Factory with registration +class OutputFormatterFactory: + _formatters: dict[str, type[Formatter]] = {} + + @classmethod + def register(cls, name: str): + def decorator(formatter_cls): + cls._formatters[name] = formatter_cls + return formatter_cls + return decorator + + @classmethod + def create(cls, name: str) -> Formatter: + return cls._formatters[name]() + +@OutputFormatterFactory.register("json") +class JsonFormatter(Formatter): + ... + +# Simple: Just use a dictionary +FORMATTERS = { + "json": JsonFormatter, + "csv": CsvFormatter, + "xml": XmlFormatter, +} + +def get_formatter(name: str) -> Formatter: + """Get formatter by name.""" + if name not in FORMATTERS: + raise ValueError(f"Unknown format: {name}") + return FORMATTERS[name]() +``` + +The factory pattern adds code without adding value here. Save patterns for when they solve real problems. + +### Pattern 2: Single Responsibility Principle + +Each class or function should have one reason to change. + +```python +# BAD: Handler does everything +class UserHandler: + async def create_user(self, request: Request) -> Response: + # HTTP parsing + data = await request.json() + + # Validation + if not data.get("email"): + return Response({"error": "email required"}, status=400) + + # Database access + user = await db.execute( + "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *", + data["email"], data["name"] + ) + + # Response formatting + return Response({"id": user.id, "email": user.email}, status=201) + +# GOOD: Separated concerns +class UserService: + """Business logic only.""" + + def __init__(self, repo: UserRepository) -> None: + self._repo = repo + + async def create_user(self, data: CreateUserInput) -> User: + # Only business rules here + user = User(email=data.email, name=data.name) + return await self._repo.save(user) + +class UserHandler: + """HTTP concerns only.""" + + def __init__(self, service: UserService) -> None: + self._service = service + + async def create_user(self, request: Request) -> Response: + data = CreateUserInput(**(await request.json())) + user = await self._service.create_user(data) + return Response(user.to_dict(), status=201) +``` + +Now HTTP changes don't affect business logic, and vice versa. + +### Pattern 3: Separation of Concerns + +Organize code into distinct layers with clear responsibilities. + +``` +┌─────────────────────────────────────────────────────┐ +│ API Layer (handlers) │ +│ - Parse requests │ +│ - Call services │ +│ - Format responses │ +└─────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────┐ +│ Service Layer (business logic) │ +│ - Domain rules and validation │ +│ - Orchestrate operations │ +│ - Pure functions where possible │ +└─────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────┐ +│ Repository Layer (data access) │ +│ - SQL queries │ +│ - External API calls │ +│ - Cache operations │ +└─────────────────────────────────────────────────────┘ +``` + +Each layer depends only on layers below it: + +```python +# Repository: Data access +class UserRepository: + async def get_by_id(self, user_id: str) -> User | None: + row = await self._db.fetchrow( + "SELECT * FROM users WHERE id = $1", user_id + ) + return User(**row) if row else None + +# Service: Business logic +class UserService: + def __init__(self, repo: UserRepository) -> None: + self._repo = repo + + async def get_user(self, user_id: str) -> User: + user = await self._repo.get_by_id(user_id) + if user is None: + raise UserNotFoundError(user_id) + return user + +# Handler: HTTP concerns +@app.get("/users/{user_id}") +async def get_user(user_id: str) -> UserResponse: + user = await user_service.get_user(user_id) + return UserResponse.from_user(user) +``` + +### Pattern 4: Composition Over Inheritance + +Build behavior by combining objects rather than inheriting. + +```python +# Inheritance: Rigid and hard to test +class EmailNotificationService(NotificationService): + def __init__(self): + super().__init__() + self._smtp = SmtpClient() # Hard to mock + + def notify(self, user: User, message: str) -> None: + self._smtp.send(user.email, message) + +# Composition: Flexible and testable +class NotificationService: + """Send notifications via multiple channels.""" + + def __init__( + self, + email_sender: EmailSender, + sms_sender: SmsSender | None = None, + push_sender: PushSender | None = None, + ) -> None: + self._email = email_sender + self._sms = sms_sender + self._push = push_sender + + async def notify( + self, + user: User, + message: str, + channels: set[str] | None = None, + ) -> None: + channels = channels or {"email"} + + if "email" in channels: + await self._email.send(user.email, message) + + if "sms" in channels and self._sms and user.phone: + await self._sms.send(user.phone, message) + + if "push" in channels and self._push and user.device_token: + await self._push.send(user.device_token, message) + +# Easy to test with fakes +service = NotificationService( + email_sender=FakeEmailSender(), + sms_sender=FakeSmsSender(), +) +``` + +## Advanced Patterns + +### Pattern 5: Rule of Three + +Wait until you have three instances before abstracting. + +```python +# Two similar functions? Don't abstract yet +def process_orders(orders: list[Order]) -> list[Result]: + results = [] + for order in orders: + validated = validate_order(order) + result = process_validated_order(validated) + results.append(result) + return results + +def process_returns(returns: list[Return]) -> list[Result]: + results = [] + for ret in returns: + validated = validate_return(ret) + result = process_validated_return(validated) + results.append(result) + return results + +# These look similar, but wait! Are they actually the same? +# Different validation, different processing, different errors... +# Duplication is often better than the wrong abstraction + +# Only after a third case, consider if there's a real pattern +# But even then, sometimes explicit is better than abstract +``` + +### Pattern 6: Function Size Guidelines + +Keep functions focused. Extract when a function: + +- Exceeds 20-50 lines (varies by complexity) +- Serves multiple distinct purposes +- Has deeply nested logic (3+ levels) + +```python +# Too long, multiple concerns mixed +def process_order(order: Order) -> Result: + # 50 lines of validation... + # 30 lines of inventory check... + # 40 lines of payment processing... + # 20 lines of notification... + pass + +# Better: Composed from focused functions +def process_order(order: Order) -> Result: + """Process a customer order through the complete workflow.""" + validate_order(order) + reserve_inventory(order) + payment_result = charge_payment(order) + send_confirmation(order, payment_result) + return Result(success=True, order_id=order.id) +``` + +### Pattern 7: Dependency Injection + +Pass dependencies through constructors for testability. + +```python +from typing import Protocol + +class Logger(Protocol): + def info(self, msg: str, **kwargs) -> None: ... + def error(self, msg: str, **kwargs) -> None: ... + +class Cache(Protocol): + async def get(self, key: str) -> str | None: ... + async def set(self, key: str, value: str, ttl: int) -> None: ... + +class UserService: + """Service with injected dependencies.""" + + def __init__( + self, + repository: UserRepository, + cache: Cache, + logger: Logger, + ) -> None: + self._repo = repository + self._cache = cache + self._logger = logger + + async def get_user(self, user_id: str) -> User: + # Check cache first + cached = await self._cache.get(f"user:{user_id}") + if cached: + self._logger.info("Cache hit", user_id=user_id) + return User.from_json(cached) + + # Fetch from database + user = await self._repo.get_by_id(user_id) + if user: + await self._cache.set(f"user:{user_id}", user.to_json(), ttl=300) + + return user + +# Production +service = UserService( + repository=PostgresUserRepository(db), + cache=RedisCache(redis), + logger=StructlogLogger(), +) + +# Testing +service = UserService( + repository=InMemoryUserRepository(), + cache=FakeCache(), + logger=NullLogger(), +) +``` + +### Pattern 8: Avoiding Common Anti-Patterns + +**Don't expose internal types:** + +```python +# BAD: Leaking ORM model to API +@app.get("/users/{id}") +def get_user(id: str) -> UserModel: # SQLAlchemy model + return db.query(UserModel).get(id) + +# GOOD: Use response schemas +@app.get("/users/{id}") +def get_user(id: str) -> UserResponse: + user = db.query(UserModel).get(id) + return UserResponse.from_orm(user) +``` + +**Don't mix I/O with business logic:** + +```python +# BAD: SQL embedded in business logic +def calculate_discount(user_id: str) -> float: + user = db.query("SELECT * FROM users WHERE id = ?", user_id) + orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id) + # Business logic mixed with data access + +# GOOD: Repository pattern +def calculate_discount(user: User, order_history: list[Order]) -> float: + # Pure business logic, easily testable + if len(order_history) > 10: + return 0.15 + return 0.0 +``` diff --git a/services/ai-core-services/.agents/skills/python-project-structure/SKILL.md b/services/ai-core-services/.agents/skills/python-project-structure/SKILL.md new file mode 100644 index 0000000..504df63 --- /dev/null +++ b/services/ai-core-services/.agents/skills/python-project-structure/SKILL.md @@ -0,0 +1,252 @@ +--- +name: python-project-structure +description: Python project organization, module architecture, and public API design. Use when setting up new projects, organizing modules, defining public interfaces with __all__, or planning directory layouts. +--- + +# Python Project Structure & Module Architecture + +Design well-organized Python projects with clear module boundaries, explicit public interfaces, and maintainable directory structures. Good organization makes code discoverable and changes predictable. + +## When to Use This Skill + +- Starting a new Python project from scratch +- Reorganizing an existing codebase for clarity +- Defining module public APIs with `__all__` +- Deciding between flat and nested directory structures +- Determining test file placement strategies +- Creating reusable library packages + +## Core Concepts + +### 1. Module Cohesion + +Group related code that changes together. A module should have a single, clear purpose. + +### 2. Explicit Interfaces + +Define what's public with `__all__`. Everything not listed is an internal implementation detail. + +### 3. Flat Hierarchies + +Prefer shallow directory structures. Add depth only for genuine sub-domains. + +### 4. Consistent Conventions + +Apply naming and organization patterns uniformly across the project. + +## Quick Start + +``` +myproject/ +├── src/ +│ └── myproject/ +│ ├── __init__.py +│ ├── services/ +│ ├── models/ +│ └── api/ +├── tests/ +├── pyproject.toml +└── README.md +``` + +## Fundamental Patterns + +### Pattern 1: One Concept Per File + +Each file should focus on a single concept or closely related set of functions. Consider splitting when a file: + +- Handles multiple unrelated responsibilities +- Grows beyond 300-500 lines (varies by complexity) +- Contains classes that change for different reasons + +```python +# Good: Focused files +# user_service.py - User business logic +# user_repository.py - User data access +# user_models.py - User data structures + +# Avoid: Kitchen sink files +# user.py - Contains service, repository, models, utilities... +``` + +### Pattern 2: Explicit Public APIs with `__all__` + +Define the public interface for every module. Unlisted members are internal implementation details. + +```python +# mypackage/services/__init__.py +from .user_service import UserService +from .order_service import OrderService +from .exceptions import ServiceError, ValidationError + +__all__ = [ + "UserService", + "OrderService", + "ServiceError", + "ValidationError", +] + +# Internal helpers remain private by omission +# from .internal_helpers import _validate_input # Not exported +``` + +### Pattern 3: Flat Directory Structure + +Prefer minimal nesting. Deep hierarchies make imports verbose and navigation difficult. + +``` +# Preferred: Flat structure +project/ +├── api/ +│ ├── routes.py +│ └── middleware.py +├── services/ +│ ├── user_service.py +│ └── order_service.py +├── models/ +│ ├── user.py +│ └── order.py +└── utils/ + └── validation.py + +# Avoid: Deep nesting +project/core/internal/services/impl/user/ +``` + +Add sub-packages only when there's a genuine sub-domain requiring isolation. + +### Pattern 4: Test File Organization + +Choose one approach and apply it consistently throughout the project. + +**Option A: Colocated Tests** + +``` +src/ +├── user_service.py +├── test_user_service.py +├── order_service.py +└── test_order_service.py +``` + +Benefits: Tests live next to the code they verify. Easy to see coverage gaps. + +**Option B: Parallel Test Directory** + +``` +src/ +├── services/ +│ ├── user_service.py +│ └── order_service.py +tests/ +├── services/ +│ ├── test_user_service.py +│ └── test_order_service.py +``` + +Benefits: Clean separation between production and test code. Standard for larger projects. + +## Advanced Patterns + +### Pattern 5: Package Initialization + +Use `__init__.py` to provide a clean public interface for package consumers. + +```python +# mypackage/__init__.py +"""MyPackage - A library for doing useful things.""" + +from .core import MainClass, HelperClass +from .exceptions import PackageError, ConfigError +from .config import Settings + +__all__ = [ + "MainClass", + "HelperClass", + "PackageError", + "ConfigError", + "Settings", +] + +__version__ = "1.0.0" +``` + +Consumers can then import directly from the package: + +```python +from mypackage import MainClass, Settings +``` + +### Pattern 6: Layered Architecture + +Organize code by architectural layer for clear separation of concerns. + +``` +myapp/ +├── api/ # HTTP handlers, request/response +│ ├── routes/ +│ └── middleware/ +├── services/ # Business logic +├── repositories/ # Data access +├── models/ # Domain entities +├── schemas/ # API schemas (Pydantic) +└── config/ # Configuration +``` + +Each layer should only depend on layers below it, never above. + +### Pattern 7: Domain-Driven Structure + +For complex applications, organize by business domain rather than technical layer. + +``` +ecommerce/ +├── users/ +│ ├── models.py +│ ├── services.py +│ ├── repository.py +│ └── api.py +├── orders/ +│ ├── models.py +│ ├── services.py +│ ├── repository.py +│ └── api.py +└── shared/ + ├── database.py + └── exceptions.py +``` + +## File and Module Naming + +### Conventions + +- Use `snake_case` for all file and module names: `user_repository.py` +- Avoid abbreviations that obscure meaning: `user_repository.py` not `usr_repo.py` +- Match class names to file names: `UserService` in `user_service.py` + +### Import Style + +Use absolute imports for clarity and reliability: + +```python +# Preferred: Absolute imports +from myproject.services import UserService +from myproject.models import User + +# Avoid: Relative imports +from ..services import UserService +from . import models +``` + +Relative imports can break when modules are moved or reorganized. + +## Best Practices Summary + +1. **Keep files focused** - One concept per file, consider splitting at 300-500 lines (varies by complexity) +2. **Define `__all__` explicitly** - Make public interfaces clear +3. **Prefer flat structures** - Add depth only for genuine sub-domains +4. **Use absolute imports** - More reliable and clearer +5. **Be consistent** - Apply patterns uniformly across the project +6. **Match names to content** - File names should describe their purpose +7. **Separate concerns** - Keep layers distinct and dependencies flowing one direction +8. **Document your structure** - Include a README explaining the organization diff --git a/services/ai-core-services/.agents/skills/python-testing-patterns/SKILL.md b/services/ai-core-services/.agents/skills/python-testing-patterns/SKILL.md new file mode 100644 index 0000000..4fe8a42 --- /dev/null +++ b/services/ai-core-services/.agents/skills/python-testing-patterns/SKILL.md @@ -0,0 +1,278 @@ +--- +name: python-testing-patterns +description: Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development. Use when writing Python tests, setting up test suites, or implementing testing best practices. +--- + +# Python Testing Patterns + +Comprehensive guide to implementing robust testing strategies in Python using pytest, fixtures, mocking, parameterization, and test-driven development practices. + +## When to Use This Skill + +- Writing unit tests for Python code +- Setting up test suites and test infrastructure +- Implementing test-driven development (TDD) +- Creating integration tests for APIs and services +- Mocking external dependencies and services +- Testing async code and concurrent operations +- Setting up continuous testing in CI/CD +- Implementing property-based testing +- Testing database operations +- Debugging failing tests + +## Core Concepts + +### 1. Test Types + +- **Unit Tests**: Test individual functions/classes in isolation +- **Integration Tests**: Test interaction between components +- **Functional Tests**: Test complete features end-to-end +- **Performance Tests**: Measure speed and resource usage + +### 2. Test Structure (AAA Pattern) + +- **Arrange**: Set up test data and preconditions +- **Act**: Execute the code under test +- **Assert**: Verify the results + +### 3. Test Coverage + +- Measure what code is exercised by tests +- Identify untested code paths +- Aim for meaningful coverage, not just high percentages + +### 4. Test Isolation + +- Tests should be independent +- No shared state between tests +- Each test should clean up after itself + +## Quick Start + +```python +# test_example.py +def add(a, b): + return a + b + +def test_add(): + """Basic test example.""" + result = add(2, 3) + assert result == 5 + +def test_add_negative(): + """Test with negative numbers.""" + assert add(-1, 1) == 0 + +# Run with: pytest test_example.py +``` + +## Detailed patterns and worked examples + +Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient. + +## Testing Best Practices + +### Test Organization + +```python +# tests/ +# __init__.py +# conftest.py # Shared fixtures +# test_unit/ # Unit tests +# test_models.py +# test_utils.py +# test_integration/ # Integration tests +# test_api.py +# test_database.py +# test_e2e/ # End-to-end tests +# test_workflows.py +``` + +### Test Naming Convention + +A common pattern: `test___`. Adapt to your team's preferences. + +```python +# Pattern: test___ +def test_create_user_with_valid_data_returns_user(): + ... + +def test_create_user_with_duplicate_email_raises_conflict(): + ... + +def test_get_user_with_unknown_id_returns_none(): + ... + +# Good test names - clear and descriptive +def test_user_creation_with_valid_data(): + """Clear name describes what is being tested.""" + pass + +def test_login_fails_with_invalid_password(): + """Name describes expected behavior.""" + pass + +def test_api_returns_404_for_missing_resource(): + """Specific about inputs and expected outcomes.""" + pass + +# Bad test names - avoid these +def test_1(): # Not descriptive + pass + +def test_user(): # Too vague + pass + +def test_function(): # Doesn't explain what's tested + pass +``` + +### Testing Retry Behavior + +Verify that retry logic works correctly using mock side effects. + +```python +from unittest.mock import Mock + +def test_retries_on_transient_error(): + """Test that service retries on transient failures.""" + client = Mock() + # Fail twice, then succeed + client.request.side_effect = [ + ConnectionError("Failed"), + ConnectionError("Failed"), + {"status": "ok"}, + ] + + service = ServiceWithRetry(client, max_retries=3) + result = service.fetch() + + assert result == {"status": "ok"} + assert client.request.call_count == 3 + +def test_gives_up_after_max_retries(): + """Test that service stops retrying after max attempts.""" + client = Mock() + client.request.side_effect = ConnectionError("Failed") + + service = ServiceWithRetry(client, max_retries=3) + + with pytest.raises(ConnectionError): + service.fetch() + + assert client.request.call_count == 3 + +def test_does_not_retry_on_permanent_error(): + """Test that permanent errors are not retried.""" + client = Mock() + client.request.side_effect = ValueError("Invalid input") + + service = ServiceWithRetry(client, max_retries=3) + + with pytest.raises(ValueError): + service.fetch() + + # Only called once - no retry for ValueError + assert client.request.call_count == 1 +``` + +### Mocking Time with Freezegun + +Use freezegun to control time in tests for predictable time-dependent behavior. + +```python +from freezegun import freeze_time +from datetime import datetime, timedelta + +@freeze_time("2026-01-15 10:00:00") +def test_token_expiry(): + """Test token expires at correct time.""" + token = create_token(expires_in_seconds=3600) + assert token.expires_at == datetime(2026, 1, 15, 11, 0, 0) + +@freeze_time("2026-01-15 10:00:00") +def test_is_expired_returns_false_before_expiry(): + """Test token is not expired when within validity period.""" + token = create_token(expires_in_seconds=3600) + assert not token.is_expired() + +@freeze_time("2026-01-15 12:00:00") +def test_is_expired_returns_true_after_expiry(): + """Test token is expired after validity period.""" + token = Token(expires_at=datetime(2026, 1, 15, 11, 30, 0)) + assert token.is_expired() + +def test_with_time_travel(): + """Test behavior across time using freeze_time context.""" + with freeze_time("2026-01-01") as frozen_time: + item = create_item() + assert item.created_at == datetime(2026, 1, 1) + + # Move forward in time + frozen_time.move_to("2026-01-15") + assert item.age_days == 14 +``` + +### Test Markers + +```python +# test_markers.py +import pytest + +@pytest.mark.slow +def test_slow_operation(): + """Mark slow tests.""" + import time + time.sleep(2) + + +@pytest.mark.integration +def test_database_integration(): + """Mark integration tests.""" + pass + + +@pytest.mark.skip(reason="Feature not implemented yet") +def test_future_feature(): + """Skip tests temporarily.""" + pass + + +@pytest.mark.skipif(os.name == "nt", reason="Unix only test") +def test_unix_specific(): + """Conditional skip.""" + pass + + +@pytest.mark.xfail(reason="Known bug #123") +def test_known_bug(): + """Mark expected failures.""" + assert False + + +# Run with: +# pytest -m slow # Run only slow tests +# pytest -m "not slow" # Skip slow tests +# pytest -m integration # Run integration tests +``` + +### Coverage Reporting + +```bash +# Install coverage +pip install pytest-cov + +# Run tests with coverage +pytest --cov=myapp tests/ + +# Generate HTML report +pytest --cov=myapp --cov-report=html tests/ + +# Fail if coverage below threshold +pytest --cov=myapp --cov-fail-under=80 tests/ + +# Show missing lines +pytest --cov=myapp --cov-report=term-missing tests/ +``` + +For advanced patterns (async testing, monkeypatching, property-based testing, database testing, CI/CD integration, and configuration), see [references/advanced-patterns.md](references/advanced-patterns.md) diff --git a/services/ai-core-services/.agents/skills/python-testing-patterns/references/advanced-patterns.md b/services/ai-core-services/.agents/skills/python-testing-patterns/references/advanced-patterns.md new file mode 100644 index 0000000..da186a9 --- /dev/null +++ b/services/ai-core-services/.agents/skills/python-testing-patterns/references/advanced-patterns.md @@ -0,0 +1,411 @@ +# Python Testing Patterns — Advanced Reference + +Advanced testing patterns including async code, monkeypatching, temporary files, conftest setup, property-based testing, database testing, CI/CD integration, and configuration. + +## Pattern 6: Testing Async Code + +```python +# test_async.py +import pytest +import asyncio + +async def fetch_data(url: str) -> dict: + """Fetch data asynchronously.""" + await asyncio.sleep(0.1) + return {"url": url, "data": "result"} + + +@pytest.mark.asyncio +async def test_fetch_data(): + """Test async function.""" + result = await fetch_data("https://api.example.com") + assert result["url"] == "https://api.example.com" + assert "data" in result + + +@pytest.mark.asyncio +async def test_concurrent_fetches(): + """Test concurrent async operations.""" + urls = ["url1", "url2", "url3"] + tasks = [fetch_data(url) for url in urls] + results = await asyncio.gather(*tasks) + + assert len(results) == 3 + assert all("data" in r for r in results) + + +@pytest.fixture +async def async_client(): + """Async fixture.""" + client = {"connected": True} + yield client + client["connected"] = False + + +@pytest.mark.asyncio +async def test_with_async_fixture(async_client): + """Test using async fixture.""" + assert async_client["connected"] is True +``` + +## Pattern 7: Monkeypatch for Testing + +```python +# test_environment.py +import os +import pytest + +def get_database_url() -> str: + """Get database URL from environment.""" + return os.environ.get("DATABASE_URL", "sqlite:///:memory:") + + +def test_database_url_default(): + """Test default database URL.""" + # Will use actual environment variable if set + url = get_database_url() + assert url + + +def test_database_url_custom(monkeypatch): + """Test custom database URL with monkeypatch.""" + monkeypatch.setenv("DATABASE_URL", "postgresql://localhost/test") + assert get_database_url() == "postgresql://localhost/test" + + +def test_database_url_not_set(monkeypatch): + """Test when env var is not set.""" + monkeypatch.delenv("DATABASE_URL", raising=False) + assert get_database_url() == "sqlite:///:memory:" + + +class Config: + """Configuration class.""" + + def __init__(self): + self.api_key = "production-key" + + def get_api_key(self): + return self.api_key + + +def test_monkeypatch_attribute(monkeypatch): + """Test monkeypatching object attributes.""" + config = Config() + monkeypatch.setattr(config, "api_key", "test-key") + assert config.get_api_key() == "test-key" +``` + +## Pattern 8: Temporary Files and Directories + +```python +# test_file_operations.py +import pytest +from pathlib import Path + +def save_data(filepath: Path, data: str): + """Save data to file.""" + filepath.write_text(data) + + +def load_data(filepath: Path) -> str: + """Load data from file.""" + return filepath.read_text() + + +def test_file_operations(tmp_path): + """Test file operations with temporary directory.""" + # tmp_path is a pathlib.Path object + test_file = tmp_path / "test_data.txt" + + # Save data + save_data(test_file, "Hello, World!") + + # Verify file exists + assert test_file.exists() + + # Load and verify data + data = load_data(test_file) + assert data == "Hello, World!" + + +def test_multiple_files(tmp_path): + """Test with multiple temporary files.""" + files = { + "file1.txt": "Content 1", + "file2.txt": "Content 2", + "file3.txt": "Content 3" + } + + for filename, content in files.items(): + filepath = tmp_path / filename + save_data(filepath, content) + + # Verify all files created + assert len(list(tmp_path.iterdir())) == 3 + + # Verify contents + for filename, expected_content in files.items(): + filepath = tmp_path / filename + assert load_data(filepath) == expected_content +``` + +## Pattern 9: Custom Fixtures and Conftest + +```python +# conftest.py +"""Shared fixtures for all tests.""" +import pytest + +@pytest.fixture(scope="session") +def database_url(): + """Provide database URL for all tests.""" + return "postgresql://localhost/test_db" + + +@pytest.fixture(autouse=True) +def reset_database(database_url): + """Auto-use fixture that runs before each test.""" + # Setup: Clear database + print(f"Clearing database: {database_url}") + yield + # Teardown: Clean up + print("Test completed") + + +@pytest.fixture +def sample_user(): + """Provide sample user data.""" + return { + "id": 1, + "name": "Test User", + "email": "test@example.com" + } + + +@pytest.fixture +def sample_users(): + """Provide list of sample users.""" + return [ + {"id": 1, "name": "User 1"}, + {"id": 2, "name": "User 2"}, + {"id": 3, "name": "User 3"}, + ] + + +# Parametrized fixture +@pytest.fixture(params=["sqlite", "postgresql", "mysql"]) +def db_backend(request): + """Fixture that runs tests with different database backends.""" + return request.param + + +def test_with_db_backend(db_backend): + """This test will run 3 times with different backends.""" + print(f"Testing with {db_backend}") + assert db_backend in ["sqlite", "postgresql", "mysql"] +``` + +## Pattern 10: Property-Based Testing + +```python +# test_properties.py +from hypothesis import given, strategies as st +import pytest + +def reverse_string(s: str) -> str: + """Reverse a string.""" + return s[::-1] + + +@given(st.text()) +def test_reverse_twice_is_original(s): + """Property: reversing twice returns original.""" + assert reverse_string(reverse_string(s)) == s + + +@given(st.text()) +def test_reverse_length(s): + """Property: reversed string has same length.""" + assert len(reverse_string(s)) == len(s) + + +@given(st.integers(), st.integers()) +def test_addition_commutative(a, b): + """Property: addition is commutative.""" + assert a + b == b + a + + +@given(st.lists(st.integers())) +def test_sorted_list_properties(lst): + """Property: sorted list is ordered.""" + sorted_lst = sorted(lst) + + # Same length + assert len(sorted_lst) == len(lst) + + # All elements present + assert set(sorted_lst) == set(lst) + + # Is ordered + for i in range(len(sorted_lst) - 1): + assert sorted_lst[i] <= sorted_lst[i + 1] +``` + +## Testing Database Code + +```python +# test_database_models.py +import pytest +from sqlalchemy import create_engine, Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker, Session + +Base = declarative_base() + + +class User(Base): + """User model.""" + __tablename__ = "users" + + id = Column(Integer, primary_key=True) + name = Column(String(50)) + email = Column(String(100), unique=True) + + +@pytest.fixture(scope="function") +def db_session() -> Session: + """Create in-memory database for testing.""" + engine = create_engine("sqlite:///:memory:") + Base.metadata.create_all(engine) + + SessionLocal = sessionmaker(bind=engine) + session = SessionLocal() + + yield session + + session.close() + + +def test_create_user(db_session): + """Test creating a user.""" + user = User(name="Test User", email="test@example.com") + db_session.add(user) + db_session.commit() + + assert user.id is not None + assert user.name == "Test User" + + +def test_query_user(db_session): + """Test querying users.""" + user1 = User(name="User 1", email="user1@example.com") + user2 = User(name="User 2", email="user2@example.com") + + db_session.add_all([user1, user2]) + db_session.commit() + + users = db_session.query(User).all() + assert len(users) == 2 + + +def test_unique_email_constraint(db_session): + """Test unique email constraint.""" + from sqlalchemy.exc import IntegrityError + + user1 = User(name="User 1", email="same@example.com") + user2 = User(name="User 2", email="same@example.com") + + db_session.add(user1) + db_session.commit() + + db_session.add(user2) + + with pytest.raises(IntegrityError): + db_session.commit() +``` + +## CI/CD Integration + +```yaml +# .github/workflows/test.yml +name: Tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install -e ".[dev]" + pip install pytest pytest-cov + + - name: Run tests + run: | + pytest --cov=myapp --cov-report=xml + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml +``` + +## Configuration Files + +```ini +# pytest.ini +[pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = + -v + --strict-markers + --tb=short + --cov=myapp + --cov-report=term-missing +markers = + slow: marks tests as slow + integration: marks integration tests + unit: marks unit tests + e2e: marks end-to-end tests +``` + +```toml +# pyproject.toml +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +addopts = [ + "-v", + "--cov=myapp", + "--cov-report=term-missing", +] + +[tool.coverage.run] +source = ["myapp"] +omit = ["*/tests/*", "*/migrations/*"] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "raise AssertionError", + "raise NotImplementedError", +] +``` diff --git a/services/ai-core-services/.agents/skills/python-testing-patterns/references/details.md b/services/ai-core-services/.agents/skills/python-testing-patterns/references/details.md new file mode 100644 index 0000000..cc494e9 --- /dev/null +++ b/services/ai-core-services/.agents/skills/python-testing-patterns/references/details.md @@ -0,0 +1,349 @@ +# python-testing-patterns — detailed patterns and worked examples + +## Fundamental Patterns + +### Pattern 1: Basic pytest Tests + +```python +# test_calculator.py +import pytest + +class Calculator: + """Simple calculator for testing.""" + + def add(self, a: float, b: float) -> float: + return a + b + + def subtract(self, a: float, b: float) -> float: + return a - b + + def multiply(self, a: float, b: float) -> float: + return a * b + + def divide(self, a: float, b: float) -> float: + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + + +def test_addition(): + """Test addition.""" + calc = Calculator() + assert calc.add(2, 3) == 5 + assert calc.add(-1, 1) == 0 + assert calc.add(0, 0) == 0 + + +def test_subtraction(): + """Test subtraction.""" + calc = Calculator() + assert calc.subtract(5, 3) == 2 + assert calc.subtract(0, 5) == -5 + + +def test_multiplication(): + """Test multiplication.""" + calc = Calculator() + assert calc.multiply(3, 4) == 12 + assert calc.multiply(0, 5) == 0 + + +def test_division(): + """Test division.""" + calc = Calculator() + assert calc.divide(6, 3) == 2 + assert calc.divide(5, 2) == 2.5 + + +def test_division_by_zero(): + """Test division by zero raises error.""" + calc = Calculator() + with pytest.raises(ValueError, match="Cannot divide by zero"): + calc.divide(5, 0) +``` + +### Pattern 2: Fixtures for Setup and Teardown + +```python +# test_database.py +import pytest +from typing import Generator + +class Database: + """Simple database class.""" + + def __init__(self, connection_string: str): + self.connection_string = connection_string + self.connected = False + + def connect(self): + """Connect to database.""" + self.connected = True + + def disconnect(self): + """Disconnect from database.""" + self.connected = False + + def query(self, sql: str) -> list: + """Execute query.""" + if not self.connected: + raise RuntimeError("Not connected") + return [{"id": 1, "name": "Test"}] + + +@pytest.fixture +def db() -> Generator[Database, None, None]: + """Fixture that provides connected database.""" + # Setup + database = Database("sqlite:///:memory:") + database.connect() + + # Provide to test + yield database + + # Teardown + database.disconnect() + + +def test_database_query(db): + """Test database query with fixture.""" + results = db.query("SELECT * FROM users") + assert len(results) == 1 + assert results[0]["name"] == "Test" + + +@pytest.fixture(scope="session") +def app_config(): + """Session-scoped fixture - created once per test session.""" + return { + "database_url": "postgresql://localhost/test", + "api_key": "test-key", + "debug": True + } + + +@pytest.fixture(scope="module") +def api_client(app_config): + """Module-scoped fixture - created once per test module.""" + # Setup expensive resource + client = {"config": app_config, "session": "active"} + yield client + # Cleanup + client["session"] = "closed" + + +def test_api_client(api_client): + """Test using api client fixture.""" + assert api_client["session"] == "active" + assert api_client["config"]["debug"] is True +``` + +### Pattern 3: Parameterized Tests + +```python +# test_validation.py +import pytest + +def is_valid_email(email: str) -> bool: + """Check if email is valid.""" + return "@" in email and "." in email.split("@")[1] + + +@pytest.mark.parametrize("email,expected", [ + ("user@example.com", True), + ("test.user@domain.co.uk", True), + ("invalid.email", False), + ("@example.com", False), + ("user@domain", False), + ("", False), +]) +def test_email_validation(email, expected): + """Test email validation with various inputs.""" + assert is_valid_email(email) == expected + + +@pytest.mark.parametrize("a,b,expected", [ + (2, 3, 5), + (0, 0, 0), + (-1, 1, 0), + (100, 200, 300), + (-5, -5, -10), +]) +def test_addition_parameterized(a, b, expected): + """Test addition with multiple parameter sets.""" + from test_calculator import Calculator + calc = Calculator() + assert calc.add(a, b) == expected + + +# Using pytest.param for special cases +@pytest.mark.parametrize("value,expected", [ + pytest.param(1, True, id="positive"), + pytest.param(0, False, id="zero"), + pytest.param(-1, False, id="negative"), +]) +def test_is_positive(value, expected): + """Test with custom test IDs.""" + assert (value > 0) == expected +``` + +### Pattern 4: Mocking with unittest.mock + +```python +# test_api_client.py +import pytest +from unittest.mock import Mock, patch, MagicMock +import requests + +class APIClient: + """Simple API client.""" + + def __init__(self, base_url: str): + self.base_url = base_url + + def get_user(self, user_id: int) -> dict: + """Fetch user from API.""" + response = requests.get(f"{self.base_url}/users/{user_id}") + response.raise_for_status() + return response.json() + + def create_user(self, data: dict) -> dict: + """Create new user.""" + response = requests.post(f"{self.base_url}/users", json=data) + response.raise_for_status() + return response.json() + + +def test_get_user_success(): + """Test successful API call with mock.""" + client = APIClient("https://api.example.com") + + mock_response = Mock() + mock_response.json.return_value = {"id": 1, "name": "John Doe"} + mock_response.raise_for_status.return_value = None + + with patch("requests.get", return_value=mock_response) as mock_get: + user = client.get_user(1) + + assert user["id"] == 1 + assert user["name"] == "John Doe" + mock_get.assert_called_once_with("https://api.example.com/users/1") + + +def test_get_user_not_found(): + """Test API call with 404 error.""" + client = APIClient("https://api.example.com") + + mock_response = Mock() + mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found") + + with patch("requests.get", return_value=mock_response): + with pytest.raises(requests.HTTPError): + client.get_user(999) + + +@patch("requests.post") +def test_create_user(mock_post): + """Test user creation with decorator syntax.""" + client = APIClient("https://api.example.com") + + mock_post.return_value.json.return_value = {"id": 2, "name": "Jane Doe"} + mock_post.return_value.raise_for_status.return_value = None + + user_data = {"name": "Jane Doe", "email": "jane@example.com"} + result = client.create_user(user_data) + + assert result["id"] == 2 + mock_post.assert_called_once() + call_args = mock_post.call_args + assert call_args.kwargs["json"] == user_data +``` + +### Pattern 5: Testing Exceptions + +```python +# test_exceptions.py +import pytest + +def divide(a: float, b: float) -> float: + """Divide a by b.""" + if b == 0: + raise ZeroDivisionError("Division by zero") + if not isinstance(a, (int, float)) or not isinstance(b, (int, float)): + raise TypeError("Arguments must be numbers") + return a / b + + +def test_zero_division(): + """Test exception is raised for division by zero.""" + with pytest.raises(ZeroDivisionError): + divide(10, 0) + + +def test_zero_division_with_message(): + """Test exception message.""" + with pytest.raises(ZeroDivisionError, match="Division by zero"): + divide(5, 0) + + +def test_type_error(): + """Test type error exception.""" + with pytest.raises(TypeError, match="must be numbers"): + divide("10", 5) + + +def test_exception_info(): + """Test accessing exception info.""" + with pytest.raises(ValueError) as exc_info: + int("not a number") + + assert "invalid literal" in str(exc_info.value) +``` + +For advanced patterns including async testing, monkeypatching, temporary files, conftest setup, property-based testing, database testing, CI/CD integration, and configuration files, see [references/advanced-patterns.md](references/advanced-patterns.md) + +## Test Design Principles + +### One Behavior Per Test + +Each test should verify exactly one behavior. This makes failures easy to diagnose and tests easy to maintain. + +```python +# BAD - testing multiple behaviors +def test_user_service(): + user = service.create_user(data) + assert user.id is not None + assert user.email == data["email"] + updated = service.update_user(user.id, {"name": "New"}) + assert updated.name == "New" + +# GOOD - focused tests +def test_create_user_assigns_id(): + user = service.create_user(data) + assert user.id is not None + +def test_create_user_stores_email(): + user = service.create_user(data) + assert user.email == data["email"] + +def test_update_user_changes_name(): + user = service.create_user(data) + updated = service.update_user(user.id, {"name": "New"}) + assert updated.name == "New" +``` + +### Test Error Paths + +Always test failure cases, not just happy paths. + +```python +def test_get_user_raises_not_found(): + with pytest.raises(UserNotFoundError) as exc_info: + service.get_user("nonexistent-id") + + assert "nonexistent-id" in str(exc_info.value) + +def test_create_user_rejects_invalid_email(): + with pytest.raises(ValueError, match="Invalid email format"): + service.create_user({"email": "not-an-email"}) +``` diff --git a/services/ai-core-services/.dockerignore b/services/ai-core-services/.dockerignore new file mode 100644 index 0000000..333e70b --- /dev/null +++ b/services/ai-core-services/.dockerignore @@ -0,0 +1,12 @@ +.venv +.venv-test +__pycache__ +*.py[cod] +.pytest_cache +.coverage +htmlcov +.git +.agents +tests +*.md +!README.md diff --git a/services/ai-core-services/.env.example b/services/ai-core-services/.env.example index ec478e9..cf2ef3f 100644 --- a/services/ai-core-services/.env.example +++ b/services/ai-core-services/.env.example @@ -39,3 +39,6 @@ MINIO_SECRET_KEY=minio_password MINIO_SECURE=false MINIO_BUCKET_NAME=resumes WEBSOCKET_URL=wss://dummy-ezscreen.ngrok.io/audio + +# Text-layer PDF extraction only (recommended on t3.medium). Set true for scanned/image PDFs. +DOCLING_DO_OCR=false diff --git a/services/ai-core-services/README.md b/services/ai-core-services/README.md index 7124bb4..8d208ed 100644 --- a/services/ai-core-services/README.md +++ b/services/ai-core-services/README.md @@ -30,33 +30,45 @@ The microservice follows a **modular domain structure** with **shared generic fu services/ai-core-services/src/ ├── main.py # Central FastAPI application entrypoint ├── core/ # Configuration, settings loader, & environment variables +│ ├── config.py +│ ├── logger.py +│ └── storage.py # MinIO object storage client │ -├── common/ # SHARED GENERIC UTILITIES & FUNCTIONS -│ ├── llm_client.py # Shared Gemma4:31b LLM engine wrapper & JSON repair parser -│ ├── file_extractor.py # Shared PDF / DOCX text extraction helper -│ ├── storage.py # Shared MinIO object storage client -│ └── logger.py # Shared structured logging utility +├── common/ # Shared generic utilities +│ └── llm_utils.py # LLM JSON parsing helpers │ -├── api/ # ABSTRACTED API CONTROLLERS (/internal/v1/*) -│ ├── router.py # Master API router aggregating all internal routes -│ └── v1/ # Clean HTTP request controllers & Pydantic DTO validation +├── llm/ # Ollama / Gemma4 LLM client wrapper +│ └── client.py +│ +├── api/ # API controllers (/internal/v1/*) +│ └── v1/ │ ├── parsing.py │ ├── matching.py -│ ├── screening.py -│ ├── bot.py -│ └── analysis.py +│ ├── question_generation.py +│ └── meeting_bot.py │ -└── DOMAIN MODULES (Self-Contained Implementation): - ├── parsing/ # 1. Direct In-Memory JD & Resume Parsing logic - ├── matching_result/ # 2. Custom Candidate-JD Weighted Scoring calculation - ├── question_generation/ # 3. Pre-Call Candidate-Tailored Question Generator - ├── screening_pipeline/ # 4. Real-Time Audio STT & TTS Pipelines - ├── meeting_bot/ # 5. Attendee.dev Meeting Bot Dispatcher & WebSockets - └── interview_analysis/ # 6. Candidate Interview Transcript Evaluator +└── DOMAIN MODULES: + ├── parsing/ # JD & resume parsing + ├── job_fit_analysis/ # Resume-JD matching & scoring + ├── question_generation/ # Pre-call interview question generator + ├── screening_pipeline/ # Real-time STT & TTS pipelines + ├── meeting_bot/ # Attendee.dev bot dispatcher & WebSockets + └── interview_analysis/ # Post-call transcript evaluator ``` --- +## Agent Skills & Cursor Rules + +Python conventions for AI-assisted development live in: + +- **Skills**: `.agents/skills/` (design patterns, project structure, testing) +- **Compiled guide**: `.agents/AGENTS.md` + +Run tests: `cd services/ai-core-services && uv run pytest` + +--- + ## 3. Microservice Internal API Map All endpoints are private inter-service APIs (`/internal/v1/*`) invoked internally by `apps/core-api`. diff --git a/services/ai-core-services/pyproject.toml b/services/ai-core-services/pyproject.toml index 7c8a13a..e4cef5c 100644 --- a/services/ai-core-services/pyproject.toml +++ b/services/ai-core-services/pyproject.toml @@ -32,4 +32,14 @@ build-backend = "hatchling.build" packages = ["src"] [tool.uv] -dev-dependencies = [] +dev-dependencies = [ + "pytest>=8.0", + "pytest-asyncio>=0.23", + "pytest-cov>=5.0", + "httpx>=0.24", +] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] +pythonpath = ["."] diff --git a/services/ai-core-services/skills-lock.json b/services/ai-core-services/skills-lock.json new file mode 100644 index 0000000..1fefa16 --- /dev/null +++ b/services/ai-core-services/skills-lock.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "skills": { + "python-design-patterns": { + "source": "wshobson/agents", + "sourceType": "github", + "skillPath": "plugins/python-development/skills/python-design-patterns/SKILL.md", + "computedHash": "e9340dc302515d844fb5c4d6a14da72bed62ef50ab8b56540dd80a1a22a3c059" + }, + "python-project-structure": { + "source": "wshobson/agents", + "sourceType": "github", + "skillPath": "plugins/python-development/skills/python-project-structure/SKILL.md", + "computedHash": "2f4f560964af8ecad4d54666c044baa54671d0abd1136c848b539befe24db170" + }, + "python-testing-patterns": { + "source": "wshobson/agents", + "sourceType": "github", + "skillPath": "plugins/python-development/skills/python-testing-patterns/SKILL.md", + "computedHash": "1306eea409e7eff2288d7658806cb508f18339a0ee9a686d324a0fd2570eae55" + } + } +} diff --git a/services/ai-core-services/src/api/v1/question_generation.py b/services/ai-core-services/src/api/v1/question_generation.py index 2f74677..bd4118b 100644 --- a/services/ai-core-services/src/api/v1/question_generation.py +++ b/services/ai-core-services/src/api/v1/question_generation.py @@ -9,14 +9,12 @@ @router.post("/generate", response_model=GenerateQuestionsResponse) async def generate_questions_endpoint(request: GenerateQuestionsRequest): """ - Generates tailored interview screening questions for a candidate. + Generates tailored interview screening questions. - Core API sends parsed_resume, parsed_jd, and job_fit_analysis. - AI service builds the prompt, calls the LLM, and returns the questions. - Core API is responsible for saving the questions to interview_session.generated_questions. + For candidate sessions, Core API sends parsed_jd and job_fit_analysis. + For job publish banks, send parsed_jd only (job id as interview_session_id). """ logger.info( f"Received question generation request for session: {request.interview_session_id}" ) - return await question_generator.generate(request) diff --git a/services/ai-core-services/src/core/config.py b/services/ai-core-services/src/core/config.py index 231c668..5edb3b2 100644 --- a/services/ai-core-services/src/core/config.py +++ b/services/ai-core-services/src/core/config.py @@ -48,6 +48,9 @@ class Settings(BaseSettings): kokoro_api_url: str | None = None kokoro_api_key: str | None = None + # Docling: disable OCR for text-based PDF resumes (much faster, lower RAM on CPU). + docling_do_ocr: bool = False + model_config = SettingsConfigDict( env_file=(str(BASE_DIR / ".env"), str(BASE_DIR.parent.parent / ".env")), env_file_encoding="utf-8", diff --git a/services/ai-core-services/src/job_fit_analysis/__init__.py b/services/ai-core-services/src/job_fit_analysis/__init__.py new file mode 100644 index 0000000..61e98f2 --- /dev/null +++ b/services/ai-core-services/src/job_fit_analysis/__init__.py @@ -0,0 +1,19 @@ +from src.job_fit_analysis.score_calculator import recalculate_scores +from src.job_fit_analysis.schemas import ( + MatchRequest, + MatchResponse, + MatchScore, + ScoreBreakdown, + SkillCategorization, + SkillMatchExperience, +) + +__all__ = [ + "recalculate_scores", + "MatchRequest", + "MatchResponse", + "MatchScore", + "ScoreBreakdown", + "SkillCategorization", + "SkillMatchExperience", +] diff --git a/services/ai-core-services/src/job_fit_analysis/matcher.py b/services/ai-core-services/src/job_fit_analysis/matcher.py index 156c902..067c1b5 100644 --- a/services/ai-core-services/src/job_fit_analysis/matcher.py +++ b/services/ai-core-services/src/job_fit_analysis/matcher.py @@ -1,224 +1,42 @@ import json -from src.llm.client import OllamaClient -from src.core.logger import logger -from src.parsing.schemas import ParsedResumeData, ParsedJDData +from typing import Optional + from src.common.llm_utils import parse_llm_json +from src.core.logger import logger +from src.job_fit_analysis.prompt_builder import matching_prompt_builder +from src.job_fit_analysis.score_calculator import recalculate_scores +from src.llm.client import OllamaClient +from src.parsing.schemas import ParsedJDData, ParsedResumeData + class JobFitMatcher: - def __init__(self): - self.llm_client = OllamaClient() + """Compares parsed resume against parsed JD using LLM, with deterministic score correction.""" + + def __init__(self, llm_client: Optional[OllamaClient] = None): + self.llm_client = llm_client or OllamaClient() async def analyze_fit(self, resume_data: ParsedResumeData, jd_data: ParsedJDData) -> dict: """Compares the parsed resume against the parsed JD using LLM.""" + response = None try: - prompt = self._build_matching_prompt(resume_data, jd_data) - + prompt = matching_prompt_builder.build(resume_data, jd_data) + logger.info("Sending job fit analysis prompt to LLM") - response = await self.llm_client.openai_chat_generate(prompt=prompt, temperature=0.1, timeout=120.0) - + response = await self.llm_client.openai_chat_generate( + prompt=prompt, temperature=0.1, timeout=120.0 + ) + match_result = parse_llm_json(response.response) - - # Recalculate scores to prevent LLM math hallucinations - self._recalculate_scores(match_result) - + recalculate_scores(match_result) + return match_result except json.JSONDecodeError as e: - logger.error(f"Failed to parse Matcher JSON output: {e}\nRaw Output: {response.response}") - raise ValueError("LLM returned invalid matching JSON") + raw = response.response if response else "N/A" + logger.error(f"Failed to parse Matcher JSON output: {e}\nRaw Output: {raw}") + raise ValueError("LLM returned invalid matching JSON") from e except Exception as e: logger.error(f"Unexpected error during matching: {e}") raise - def _build_matching_prompt(self, resume_data: ParsedResumeData, jd_data: ParsedJDData) -> str: - prompt = """ -You are an expert technical recruiter and data analyst. -Compare the given candidate resume with the job description and calculate a match score based strictly on the predefined evaluation criteria below. -Return STRICT JSON only, no commentary. - -### Candidate Resume (parsed JSON): -__RESUME_JSON__ - -### Job Description (parsed JSON): -__JD_JSON__ - -### Predefined Evaluation Criteria (Total: 100 Points) - -1. Must-Have Skills (40 Points): - - Calculate the percentage of JD must-have skills found in the resume. - - Multiply that percentage by 40. - - A skill is considered found (and MUST be placed in `matched_skills`) if it is present anywhere in the candidate's `primary_skills` or `secondary_skills` arrays, REGARDLESS of whether they have 0.0 years of experience with it. If they listed it, they possess the baseline skill. - - Normalize equivalent technology names before matching. - - Do not infer a skill from a job title. - -2. Relevant Experience (30 Points Total: 20 for Must-Have, 10 for Good-To-Have): - - Calculate Relevant Experience by comparing the candidate's `skill_experience` array with the JD's must-have and good-to-have skill requirements. - - For each JD skill, identify the candidate's candidate_years from the parsed_resume's `skill_experience` array. If the skill is not in the array, candidate_years = 0.0. - - Calculate the skill_experience_ratio for each JD skill using these rules: - * If the required experience is NOT mentioned in the JD for a particular skill AND the candidate possesses the skill (i.e., it is in matched_skills): give ratio as 1.0 (even if candidate_years is 0.0) - * If the candidate does NOT possess the skill (i.e., it is in missing_skills): give ratio as 0.0 - * If the required experience IS mentioned in the JD for a particular skill and the candidate has LESS experience than required: give ratio as candidate_years / required_years - * If the required experience IS mentioned in the JD for a particular skill and the candidate has MORE or EQUAL experience than required: give ratio as 1.0 - - Calculate must-have experience strictly using this exact formula: - must_have_experience_score = (Sum of all skill_experience_ratios for must-have skills / total number of must-have skills) * 20 - - Calculate good-to-have experience strictly using this exact formula: - good_to_have_experience_score = (Sum of all skill_experience_ratios for good-to-have skills / total number of good-to-have skills) * 10 - (If there are no good-to-have skills in the JD, good_to_have_experience_score = 10) - - raw_experience = must_have_experience_score + good_to_have_experience_score - - Round raw_experience to 2 decimal places. - - For each skill in `must_have_experience` and `good_to_have_experience`, set `meets_requirement` to `true` ONLY IF `skill_experience_ratio` >= 1.0, otherwise `false`. - - Set `experience_match` to `true` ONLY IF `raw_experience` is >= 20.0. Otherwise, set it to `false`. - -3. Good-to-Have Skills (20 Points): - - Calculate the percentage of JD good-to-have skills found. - - Multiply that percentage by 20. - - A skill is considered found (and MUST be placed in `matched_skills`) if it is present anywhere in the candidate's `primary_skills` or `secondary_skills` arrays, REGARDLESS of whether they have 0.0 years of experience with it. - - Normalize equivalent technology names before matching. - - Do not infer a skill from a job title. - -4. Qualifications & Domain (Maximum 10 Points): - - Award 10 points if they have at least one exact degree/qualification match. - - Award 5 points if their best match is a somewhat related field. - - Award 5 points if they have a relevant certificate that comes under qualifications. - - Award 0 points if completely unrelated and lacking relevant certificates. - - Consider domain expertise if relevant to the JD. - - If the JD has no qualification requirements, qualifications_score = 10 and qualification_match = true. - -### Instructions: - -1. Compare candidate's skills with JD must-have and good-to-have skills. -2. Calculate Relevant Experience specifically using the candidate's `skill_experience` array against the JD's must-have and good-to-have skills and their required years. -3. Systematically calculate the raw score for each of the 4 criteria based on their respective weights (40, 30, 20, 10). -4. CRITICAL: Output the raw scores first, then convert them into a consolidated, out-of-10.0 scale for the `score_breakdown`: - - `raw_must_have_skills`: max 40.0 - - `raw_good_to_have_skills`: max 20.0 - - `raw_experience`: max 30.0 - - `raw_qualifications`: max 10.0 - - `skills_score` = (raw_must_have_skills + raw_good_to_have_skills) / 6 - - `experience_score` = raw_experience / 3 - - `qualifications_score` = raw_qualifications / 1 - (e.g., if raw_must_have_skills is 32.0 and raw_good_to_have_skills is 15.0, skills_score is 7.83) -5. Calculate the final `match_score` out of 10.0 based on the total raw points: match_score = (raw_must_have_skills + raw_experience + raw_good_to_have_skills + raw_qualifications) / 10. -6. Output highly detailed analysis in plain, human-readable language suitable for a non-technical recruiter. DO NOT use technical terms like "ratio", "score", "formula", "0.0", or "1.0". Instead, write naturally. Provide the analysis in three parts: - - `reasoning`: 3-4 bullet points summarizing the overall fit (e.g. "The candidate matches all core must-have skills...", "Qualification requirements fully met..."). CRITICAL: If the candidate is slightly under the required experience for a particular skill (e.g., 2-3 months under the requirement), you MUST explicitly mention this slight gap here in the reasoning. - - `strengths`: 4-5 bullet points highlighting the candidate's strongest alignments with the JD. CRITICAL: If the candidate is "overqualified" (e.g., having 14 years of experience for a 3-6 year role), this MUST be listed as a massive STRENGTH (e.g., "Brings a veteran level of expertise..."), NEVER as a concern. - - `concerns`: 4-5 bullet points highlighting gaps, missing skills, or shortfalls in experience. CRITICAL: If the JD requires a balanced skill set but experience is heavily skewed, point this out. NEVER list being "overqualified" as a concern. If no concerns, provide 1 bullet saying "No major concerns identified." -7. CRITICAL RULE: Never put a skill in `missing_skills` if it exists in `primary_skills` or `secondary_skills`, even if the candidate has 0.0 years of experience with it. If it is in their skills array, it MUST go into `matched_skills`. - -### Output Format (STRICT JSON): - -{ - "score_breakdown": { - "raw_must_have_skills": 32.0, - "raw_good_to_have_skills": 15.0, - "raw_experience": 24.5, - "raw_qualifications": 10.0, - "skills_score": 7.83, - "experience_score": 8.16, - "qualifications_score": 10.0 - }, - "match_score": 8.15, - "reasoning": [ - "Overall strong fit with technical alignment across most core skills.", - "Qualification requirements fully met with a Bachelor's degree in Computer Science.", - "Slight gaps in cloud infrastructure experience, but solid foundation in backend development." - ], - "strengths": [ - "The candidate matches core must-have skills including Java, Python, and SQL.", - "Strong hands-on experience in Java (14 years), well exceeding the job requirements.", - "Good coverage of nice-to-have skills, with practical experience in Docker and Kubernetes.", - "Extensive domain expertise in E-commerce architecture." - ], - "concerns": [ - "Missing critical must-have skill: AWS — not found anywhere in the candidate's resume.", - "Experience gap in Spring Boot: the candidate has 2 years of hands-on experience, but the role requires at least 3 years.", - "Imbalanced experience for a Full Stack role: candidate has 4 years of frontend (React) experience, but only 3 months of backend (Node.js) experience.", - "The candidate lists PostgreSQL as a skill but has no professional work experience using it in any of their roles." - ], - "matched_skills": { - "must_have": ["..."], - "good_to_have": ["..."] - }, - "missing_skills": { - "must_have": ["..."], - "good_to_have": ["..."] - }, - "must_have_experience": [ - { - "skill": "Java", - "required_years": 3.0, - "candidate_years": 4.0, - "skill_experience_ratio": 1.0, - "meets_requirement": true - } - ], - "good_to_have_experience": [ - { - "skill": "Docker", - "required_years": null, - "candidate_years": 2.0, - "skill_experience_ratio": 1.0, - "meets_requirement": true - } - ], - "qualification_match": true, - "experience_match": false -} -""" - prompt = prompt.replace("__RESUME_JSON__", json.dumps(resume_data.model_dump(), indent=2)) - prompt = prompt.replace("__JD_JSON__", json.dumps(jd_data.model_dump(), indent=2)) - return prompt - - def _recalculate_scores(self, match_result: dict) -> None: - """Overrides the LLM's mathematical score calculation using strict Python math based on the generated arrays.""" - if "score_breakdown" not in match_result: - match_result["score_breakdown"] = {} - - must_have_exp = match_result.get("must_have_experience", []) - good_to_have_exp = match_result.get("good_to_have_experience", []) - - total_must_have = len(must_have_exp) - total_good_to_have = len(good_to_have_exp) - - # 1. Must-Have Skills (40 Points) - matched_must_have = len(match_result.get("matched_skills", {}).get("must_have", [])) - raw_must_have = (matched_must_have / total_must_have) * 40.0 if total_must_have > 0 else 40.0 - - # 2. Good-To-Have Skills (20 Points) - matched_good = len(match_result.get("matched_skills", {}).get("good_to_have", [])) - raw_good_to_have = (matched_good / total_good_to_have) * 20.0 if total_good_to_have > 0 else 20.0 - - # 3. Experience (30 Points) - sum_must_have_ratios = sum(float(skill.get("skill_experience_ratio", 0.0)) for skill in must_have_exp) - raw_exp_must_have = (sum_must_have_ratios / total_must_have) * 20.0 if total_must_have > 0 else 20.0 - - sum_good_ratios = sum(float(skill.get("skill_experience_ratio", 0.0)) for skill in good_to_have_exp) - raw_exp_good = (sum_good_ratios / total_good_to_have) * 10.0 if total_good_to_have > 0 else 10.0 - - raw_exp = raw_exp_must_have + raw_exp_good - - # 4. Qualifications (10 Points) - # Trust LLM for qualification match (either 10.0 or 0.0 based on criteria) - raw_qual = float(match_result.get("score_breakdown", {}).get("raw_qualifications", 10.0)) - - # Update raw score_breakdown - match_result["score_breakdown"]["raw_must_have_skills"] = round(raw_must_have, 2) - match_result["score_breakdown"]["raw_good_to_have_skills"] = round(raw_good_to_have, 2) - match_result["score_breakdown"]["raw_experience"] = round(raw_exp, 2) - match_result["score_breakdown"]["raw_qualifications"] = round(raw_qual, 2) - - # Scale to 10 points - skills_score = ((raw_must_have + raw_good_to_have) / 60.0) * 10.0 - exp_score = (raw_exp / 30.0) * 10.0 - qual_score = (raw_qual / 10.0) * 10.0 - - match_result["score_breakdown"]["skills_score"] = round(skills_score, 2) - match_result["score_breakdown"]["experience_score"] = round(exp_score, 2) - match_result["score_breakdown"]["qualifications_score"] = round(qual_score, 2) - - total_raw = raw_must_have + raw_good_to_have + raw_exp + raw_qual - match_result["match_score"] = round(total_raw / 10.0, 2) - - # Enforce boolean logic threshold - match_result["experience_match"] = raw_exp >= 20.0 job_fit_matcher = JobFitMatcher() diff --git a/services/ai-core-services/src/job_fit_analysis/prompt_builder.py b/services/ai-core-services/src/job_fit_analysis/prompt_builder.py new file mode 100644 index 0000000..a87b9cb --- /dev/null +++ b/services/ai-core-services/src/job_fit_analysis/prompt_builder.py @@ -0,0 +1,151 @@ +import json + +from src.parsing.schemas import ParsedJDData, ParsedResumeData + +MATCHING_PROMPT_TEMPLATE = """ +You are an expert technical recruiter and data analyst. +Compare the given candidate resume with the job description and calculate a match score based strictly on the predefined evaluation criteria below. +Return STRICT JSON only, no commentary. + +### Candidate Resume (parsed JSON): +__RESUME_JSON__ + +### Job Description (parsed JSON): +__JD_JSON__ + +### Predefined Evaluation Criteria (Total: 100 Points) + +1. Must-Have Skills (40 Points): + - Calculate the percentage of JD must-have skills found in the resume. + - Multiply that percentage by 40. + - A skill is considered found (and MUST be placed in `matched_skills`) if it is present anywhere in the candidate's `primary_skills` or `secondary_skills` arrays, REGARDLESS of whether they have 0.0 years of experience with it. If they listed it, they possess the baseline skill. + - Normalize equivalent technology names before matching. + - Do not infer a skill from a job title. + +2. Relevant Experience (30 Points Total: 20 for Must-Have, 10 for Good-To-Have): + - Calculate Relevant Experience by comparing the candidate's `skill_experience` array with the JD's must-have and good-to-have skill requirements. + - For each JD skill, identify the candidate's candidate_years from the parsed_resume's `skill_experience` array. If the skill is not in the array, candidate_years = 0.0. + - Calculate the skill_experience_ratio for each JD skill using these rules: + * If the required experience is NOT mentioned in the JD for a particular skill AND the candidate possesses the skill (i.e., it is in matched_skills): give ratio as 1.0 (even if candidate_years is 0.0) + * If the candidate does NOT possess the skill (i.e., it is in missing_skills): give ratio as 0.0 + * If the required experience IS mentioned in the JD for a particular skill and the candidate has LESS experience than required: give ratio as candidate_years / required_years + * If the required experience IS mentioned in the JD for a particular skill and the candidate has MORE or EQUAL experience than required: give ratio as 1.0 + - Calculate must-have experience strictly using this exact formula: + must_have_experience_score = (Sum of all skill_experience_ratios for must-have skills / total number of must-have skills) * 20 + - Calculate good-to-have experience strictly using this exact formula: + good_to_have_experience_score = (Sum of all skill_experience_ratios for good-to-have skills / total number of good-to-have skills) * 10 + (If there are no good-to-have skills in the JD, good_to_have_experience_score = 10) + - raw_experience = must_have_experience_score + good_to_have_experience_score + - Round raw_experience to 2 decimal places. + - For each skill in `must_have_experience` and `good_to_have_experience`, set `meets_requirement` to `true` ONLY IF `skill_experience_ratio` >= 1.0, otherwise `false`. + - Set `experience_match` to `true` ONLY IF `raw_experience` is >= 20.0. Otherwise, set it to `false`. + +3. Good-to-Have Skills (20 Points): + - Calculate the percentage of JD good-to-have skills found. + - Multiply that percentage by 20. + - A skill is considered found (and MUST be placed in `matched_skills`) if it is present anywhere in the candidate's `primary_skills` or `secondary_skills` arrays, REGARDLESS of whether they have 0.0 years of experience with it. + - Normalize equivalent technology names before matching. + - Do not infer a skill from a job title. + +4. Qualifications & Domain (Maximum 10 Points): + - Award 10 points if they have at least one exact degree/qualification match. + - Award 5 points if their best match is a somewhat related field. + - Award 5 points if they have a relevant certificate that comes under qualifications. + - Award 0 points if completely unrelated and lacking relevant certificates. + - Consider domain expertise if relevant to the JD. + - If the JD has no qualification requirements, qualifications_score = 10 and qualification_match = true. + +### Instructions: + +1. Compare candidate's skills with JD must-have and good-to-have skills. +2. Calculate Relevant Experience specifically using the candidate's `skill_experience` array against the JD's must-have and good-to-have skills and their required years. +3. Systematically calculate the raw score for each of the 4 criteria based on their respective weights (40, 30, 20, 10). +4. CRITICAL: Output the raw scores first, then convert them into a consolidated, out-of-10.0 scale for the `score_breakdown`: + - `raw_must_have_skills`: max 40.0 + - `raw_good_to_have_skills`: max 20.0 + - `raw_experience`: max 30.0 + - `raw_qualifications`: max 10.0 + - `skills_score` = (raw_must_have_skills + raw_good_to_have_skills) / 6 + - `experience_score` = raw_experience / 3 + - `qualifications_score` = raw_qualifications / 1 + (e.g., if raw_must_have_skills is 32.0 and raw_good_to_have_skills is 15.0, skills_score is 7.83) +5. Calculate the final `match_score` out of 10.0 based on the total raw points: match_score = (raw_must_have_skills + raw_experience + raw_good_to_have_skills + raw_qualifications) / 10. +6. Output highly detailed analysis in plain, human-readable language suitable for a non-technical recruiter. DO NOT use technical terms like "ratio", "score", "formula", "0.0", or "1.0". Instead, write naturally. Provide the analysis in three parts: + - `reasoning`: 3-4 bullet points summarizing the overall fit (e.g. "The candidate matches all core must-have skills...", "Qualification requirements fully met..."). CRITICAL: If the candidate is slightly under the required experience for a particular skill (e.g., 2-3 months under the requirement), you MUST explicitly mention this slight gap here in the reasoning. + - `strengths`: 4-5 bullet points highlighting the candidate's strongest alignments with the JD. CRITICAL: If the candidate is "overqualified" (e.g., having 14 years of experience for a 3-6 year role), this MUST be listed as a massive STRENGTH (e.g., "Brings a veteran level of expertise..."), NEVER as a concern. + - `concerns`: 4-5 bullet points highlighting gaps, missing skills, or shortfalls in experience. CRITICAL: If the JD requires a balanced skill set but experience is heavily skewed, point this out. NEVER list being "overqualified" as a concern. If no concerns, provide 1 bullet saying "No major concerns identified." +7. CRITICAL RULE: Never put a skill in `missing_skills` if it exists in `primary_skills` or `secondary_skills`, even if the candidate has 0.0 years of experience with it. If it is in their skills array, it MUST go into `matched_skills`. + +### Output Format (STRICT JSON): + +{ + "score_breakdown": { + "raw_must_have_skills": 32.0, + "raw_good_to_have_skills": 15.0, + "raw_experience": 24.5, + "raw_qualifications": 10.0, + "skills_score": 7.83, + "experience_score": 8.16, + "qualifications_score": 10.0 + }, + "match_score": 8.15, + "reasoning": [ + "Overall strong fit with technical alignment across most core skills.", + "Qualification requirements fully met with a Bachelor's degree in Computer Science.", + "Slight gaps in cloud infrastructure experience, but solid foundation in backend development." + ], + "strengths": [ + "The candidate matches core must-have skills including Java, Python, and SQL.", + "Strong hands-on experience in Java (14 years), well exceeding the job requirements.", + "Good coverage of nice-to-have skills, with practical experience in Docker and Kubernetes.", + "Extensive domain expertise in E-commerce architecture." + ], + "concerns": [ + "Missing critical must-have skill: AWS — not found anywhere in the candidate's resume.", + "Experience gap in Spring Boot: the candidate has 2 years of hands-on experience, but the role requires at least 3 years.", + "Imbalanced experience for a Full Stack role: candidate has 4 years of frontend (React) experience, but only 3 months of backend (Node.js) experience.", + "The candidate lists PostgreSQL as a skill but has no professional work experience using it in any of their roles." + ], + "matched_skills": { + "must_have": ["..."], + "good_to_have": ["..."] + }, + "missing_skills": { + "must_have": ["..."], + "good_to_have": ["..."] + }, + "must_have_experience": [ + { + "skill": "Java", + "required_years": 3.0, + "candidate_years": 4.0, + "skill_experience_ratio": 1.0, + "meets_requirement": true + } + ], + "good_to_have_experience": [ + { + "skill": "Docker", + "required_years": null, + "candidate_years": 2.0, + "skill_experience_ratio": 1.0, + "meets_requirement": true + } + ], + "qualification_match": true, + "experience_match": false +} +""" + + +class MatchingPromptBuilder: + """Builds the LLM prompt for resume-JD job fit analysis.""" + + def build(self, resume_data: ParsedResumeData, jd_data: ParsedJDData) -> str: + prompt = MATCHING_PROMPT_TEMPLATE + prompt = prompt.replace("__RESUME_JSON__", json.dumps(resume_data.model_dump(), indent=2)) + prompt = prompt.replace("__JD_JSON__", json.dumps(jd_data.model_dump(), indent=2)) + return prompt + + +matching_prompt_builder = MatchingPromptBuilder() diff --git a/services/ai-core-services/src/job_fit_analysis/score_calculator.py b/services/ai-core-services/src/job_fit_analysis/score_calculator.py new file mode 100644 index 0000000..5dd80f4 --- /dev/null +++ b/services/ai-core-services/src/job_fit_analysis/score_calculator.py @@ -0,0 +1,50 @@ +"""Deterministic score recalculation for job fit analysis. + +Pure functions — no I/O. Overrides LLM math to prevent hallucinated scores. +""" + + +def recalculate_scores(match_result: dict) -> None: + """Recalculate match scores from matched_skills and experience arrays.""" + if "score_breakdown" not in match_result: + match_result["score_breakdown"] = {} + + must_have_exp = match_result.get("must_have_experience", []) + good_to_have_exp = match_result.get("good_to_have_experience", []) + + total_must_have = len(must_have_exp) + total_good_to_have = len(good_to_have_exp) + + matched_must_have = len(match_result.get("matched_skills", {}).get("must_have", [])) + raw_must_have = (matched_must_have / total_must_have) * 40.0 if total_must_have > 0 else 40.0 + + matched_good = len(match_result.get("matched_skills", {}).get("good_to_have", [])) + raw_good_to_have = (matched_good / total_good_to_have) * 20.0 if total_good_to_have > 0 else 20.0 + + sum_must_have_ratios = sum(float(skill.get("skill_experience_ratio", 0.0)) for skill in must_have_exp) + raw_exp_must_have = (sum_must_have_ratios / total_must_have) * 20.0 if total_must_have > 0 else 20.0 + + sum_good_ratios = sum(float(skill.get("skill_experience_ratio", 0.0)) for skill in good_to_have_exp) + raw_exp_good = (sum_good_ratios / total_good_to_have) * 10.0 if total_good_to_have > 0 else 10.0 + + raw_exp = raw_exp_must_have + raw_exp_good + + raw_qual = float(match_result.get("score_breakdown", {}).get("raw_qualifications", 10.0)) + + match_result["score_breakdown"]["raw_must_have_skills"] = round(raw_must_have, 2) + match_result["score_breakdown"]["raw_good_to_have_skills"] = round(raw_good_to_have, 2) + match_result["score_breakdown"]["raw_experience"] = round(raw_exp, 2) + match_result["score_breakdown"]["raw_qualifications"] = round(raw_qual, 2) + + skills_score = ((raw_must_have + raw_good_to_have) / 60.0) * 10.0 + exp_score = (raw_exp / 30.0) * 10.0 + qual_score = (raw_qual / 10.0) * 10.0 + + match_result["score_breakdown"]["skills_score"] = round(skills_score, 2) + match_result["score_breakdown"]["experience_score"] = round(exp_score, 2) + match_result["score_breakdown"]["qualifications_score"] = round(qual_score, 2) + + total_raw = raw_must_have + raw_good_to_have + raw_exp + raw_qual + match_result["match_score"] = round(total_raw / 10.0, 2) + + match_result["experience_match"] = raw_exp >= 20.0 diff --git a/services/ai-core-services/src/parsing/__init__.py b/services/ai-core-services/src/parsing/__init__.py new file mode 100644 index 0000000..c8d8a27 --- /dev/null +++ b/services/ai-core-services/src/parsing/__init__.py @@ -0,0 +1,35 @@ +from src.parsing.experience_calculator import recalculate_experience +from src.parsing.schemas import ( + CandidateSkillExperience, + EducationCertificate, + Experience, + ExperienceRequired, + JDSkills, + ParseResumeRequest, + ParsedJDData, + ParsedJDResponse, + ParsedResumeData, + ParsedResumeResponse, + PersonalInfo, + RawJDRequest, + Role, + SkillRequirement, +) + +__all__ = [ + "recalculate_experience", + "ParseResumeRequest", + "ParsedResumeResponse", + "ParsedResumeData", + "PersonalInfo", + "EducationCertificate", + "Role", + "Experience", + "CandidateSkillExperience", + "RawJDRequest", + "ParsedJDResponse", + "ParsedJDData", + "ExperienceRequired", + "SkillRequirement", + "JDSkills", +] diff --git a/services/ai-core-services/src/parsing/docling_wrapper.py b/services/ai-core-services/src/parsing/docling_wrapper.py index 4656318..0ceddae 100644 --- a/services/ai-core-services/src/parsing/docling_wrapper.py +++ b/services/ai-core-services/src/parsing/docling_wrapper.py @@ -1,10 +1,39 @@ -from docling.document_converter import DocumentConverter +from docling.backend.pypdfium2_backend import PyPdfiumDocumentBackend +from docling.datamodel.base_models import InputFormat +from docling.datamodel.pipeline_options import PdfPipelineOptions +from docling.document_converter import DocumentConverter, PdfFormatOption +from src.core.config import settings from src.core.logger import logger + +def _build_converter() -> DocumentConverter: + if settings.docling_do_ocr: + logger.info("Initializing Docling DocumentConverter (full pipeline with OCR)") + return DocumentConverter() + + pipeline_options = PdfPipelineOptions( + do_ocr=False, + do_table_structure=False, + do_code_enrichment=False, + do_formula_enrichment=False, + ) + logger.info( + "Initializing Docling DocumentConverter (text-layer only, OCR disabled)", + extra={"do_ocr": False}, + ) + return DocumentConverter( + format_options={ + InputFormat.PDF: PdfFormatOption( + pipeline_options=pipeline_options, + backend=PyPdfiumDocumentBackend, + ) + } + ) + + class DoclingWrapper: def __init__(self): - self.converter = DocumentConverter() - logger.info("Initialized Docling DocumentConverter") + self.converter = _build_converter() def extract_markdown(self, file_path: str) -> str: """ @@ -12,6 +41,12 @@ def extract_markdown(self, file_path: str) -> str: """ logger.info(f"Docling extracting markdown from: {file_path}") result = self.converter.convert(file_path) - return result.document.export_to_markdown() + markdown = result.document.export_to_markdown() + logger.info( + "Docling extraction complete", + extra={"file_path": file_path, "markdown_chars": len(markdown)}, + ) + return markdown + docling_wrapper = DoclingWrapper() diff --git a/services/ai-core-services/src/parsing/experience_calculator.py b/services/ai-core-services/src/parsing/experience_calculator.py new file mode 100644 index 0000000..7c41c48 --- /dev/null +++ b/services/ai-core-services/src/parsing/experience_calculator.py @@ -0,0 +1,70 @@ +"""Deterministic experience recalculation for parsed resume data. + +Pure functions — no I/O. Overrides LLM date math with strict Python calculations. +""" + +from __future__ import annotations + +import calendar +import datetime + + +def _parse_date(date_str, is_end_date: bool = False) -> datetime.datetime | None: + if not date_str or str(date_str).lower() in ("present", "current", "now", "null", "none"): + return datetime.datetime.now() + date_str = str(date_str).strip() + try: + if len(date_str) == 10: + return datetime.datetime.strptime(date_str, "%Y-%m-%d") + if len(date_str) == 7: + dt = datetime.datetime.strptime(date_str, "%Y-%m") + if is_end_date: + _, last_day = calendar.monthrange(dt.year, dt.month) + dt = dt.replace(day=last_day) + return dt + if len(date_str) == 4: + dt = datetime.datetime.strptime(date_str, "%Y") + if is_end_date: + dt = dt.replace(month=12, day=31) + return dt + except ValueError: + pass + return None + + +def recalculate_experience(parsed_data: dict) -> None: + """Recalculate role years and total_years from start/end dates.""" + if not parsed_data or "experience" not in parsed_data: + return + + roles = parsed_data["experience"].get("roles", []) + if not roles: + return + + intervals = [] + for role in roles: + start_dt = _parse_date(role.get("start_date"), is_end_date=False) + end_dt = _parse_date(role.get("end_date"), is_end_date=True) + + if start_dt and end_dt and start_dt <= end_dt: + days = (end_dt - start_dt).days + role["years"] = round(days / 365.25, 1) + intervals.append([start_dt, end_dt]) + else: + role["years"] = 0.0 + + if not intervals: + parsed_data["experience"]["total_years"] = 0.0 + return + + intervals.sort(key=lambda x: x[0]) + merged = [intervals[0]] + for current in intervals[1:]: + last = merged[-1] + if current[0] <= last[1]: + last[1] = max(last[1], current[1]) + else: + merged.append(current) + + total_days = sum((iv[1] - iv[0]).days for iv in merged) + parsed_data["experience"]["total_years"] = round(total_days / 365.25, 1) diff --git a/services/ai-core-services/src/parsing/jd_parser.py b/services/ai-core-services/src/parsing/jd_parser.py index 7620029..3d24b25 100644 --- a/services/ai-core-services/src/parsing/jd_parser.py +++ b/services/ai-core-services/src/parsing/jd_parser.py @@ -1,79 +1,36 @@ import json -from src.llm.client import OllamaClient +from typing import Optional + +from src.common.llm_utils import parse_llm_json from src.core.logger import logger +from src.llm.client import OllamaClient +from src.parsing.jd_prompt_builder import jd_prompt_builder from src.parsing.schemas import ParsedJDData + class JDParser: - def __init__(self): - self.llm_client = OllamaClient() + def __init__(self, llm_client: Optional[OllamaClient] = None): + self.llm_client = llm_client or OllamaClient() async def parse_jd_text(self, jd_text: str) -> dict: """Parses JD text or JSON string using LLM to conform to ParsedJDData schema.""" - prompt = self._build_prompt(jd_text) - + prompt = jd_prompt_builder.build(jd_text) + logger.info("Sending JD parsing prompt to LLM") - response = await self.llm_client.openai_chat_generate(prompt=prompt, temperature=0.1, timeout=60.0) - - raw_json = response.response.strip() + response = await self.llm_client.openai_chat_generate( + prompt=prompt, temperature=0.1, timeout=60.0 + ) + try: - # Basic cleanup in case LLM wraps it in markdown blocks - if raw_json.startswith("```json"): - raw_json = raw_json[7:] - if raw_json.startswith("```"): - raw_json = raw_json[3:] - if raw_json.endswith("```"): - raw_json = raw_json[:-3] - - parsed_data = json.loads(raw_json.strip()) - - # Use Pydantic to ensure the structure matches before returning it as dict + parsed_data = parse_llm_json(response.response) validated_data = ParsedJDData(**parsed_data) return validated_data.model_dump() except json.JSONDecodeError as e: logger.error(f"Failed to parse LLM JSON output for JD: {e}\nRaw Output: {response.response}") - raise ValueError("LLM returned invalid JSON for JD") + raise ValueError("LLM returned invalid JSON for JD") from e except Exception as e: - logger.error(f"Failed to validate JD schema: {e}\nRaw Output: {raw_json}") - raise ValueError(f"LLM returned JSON that does not match schema: {e}") - - def _build_prompt(self, jd_text: str) -> str: - # Prevent prompt injection issues if the jd_text contains `{` or `}` - # by passing it dynamically without formatting it into the system rules directly if possible, - # but here we just replace `{` with `{{` for f-strings if needed, - # or we can use normal string replacement. - prompt = """ -You are an expert job description parser. Parse this JD systematically. -Return ONLY a JSON object in this format: -{ - "title": null, - "company": null, - "company_description": null, - "experience_required": { - "min_years": null, - "max_years": null - }, - "skills": { - "must_have": [ - {"skill": "skill1", "required_years": null}, - {"skill": "skill2", "required_years": 3.0} - ], - "good_to_have": [ - {"skill": "skill1", "required_years": null} - ] - }, - "qualifications": ["degree1", "degree2"], - "responsibilities": ["resp1", "resp2"], - "location": null, - "employment_type": "Full-time" -} - -### Extraction Rules: -- For `must_have` and `good_to_have` skills, if the JD explicitly states a number of years of experience required for that specific skill (e.g., "3 years of Java"), set `required_years` to that number. -- If no specific years are mentioned for that individual skill, set `required_years` to `null`. Do not automatically assume the global `min_years` applies unless explicitly stated. + logger.error(f"Failed to validate JD schema: {e}\nRaw Output: {response.response}") + raise ValueError(f"LLM returned JSON that does not match schema: {e}") from e -### Job Description Text: -__JD_TEXT__ -""" - return prompt.replace("__JD_TEXT__", jd_text) jd_parser = JDParser() diff --git a/services/ai-core-services/src/parsing/jd_prompt_builder.py b/services/ai-core-services/src/parsing/jd_prompt_builder.py new file mode 100644 index 0000000..0ead306 --- /dev/null +++ b/services/ai-core-services/src/parsing/jd_prompt_builder.py @@ -0,0 +1,43 @@ +JD_PROMPT_TEMPLATE = """ +You are an expert job description parser. Parse this JD systematically. +Return ONLY a JSON object in this format: +{ + "title": null, + "company": null, + "company_description": null, + "experience_required": { + "min_years": null, + "max_years": null + }, + "skills": { + "must_have": [ + {"skill": "skill1", "required_years": null}, + {"skill": "skill2", "required_years": 3.0} + ], + "good_to_have": [ + {"skill": "skill1", "required_years": null} + ] + }, + "qualifications": ["degree1", "degree2"], + "responsibilities": ["resp1", "resp2"], + "location": null, + "employment_type": "Full-time" +} + +### Extraction Rules: +- For `must_have` and `good_to_have` skills, if the JD explicitly states a number of years of experience required for that specific skill (e.g., "3 years of Java"), set `required_years` to that number. +- If no specific years are mentioned for that individual skill, set `required_years` to `null`. Do not automatically assume the global `min_years` applies unless explicitly stated. + +### Job Description Text: +__JD_TEXT__ +""" + + +class JDPromptBuilder: + """Builds the LLM prompt for job description parsing.""" + + def build(self, jd_text: str) -> str: + return JD_PROMPT_TEMPLATE.replace("__JD_TEXT__", jd_text) + + +jd_prompt_builder = JDPromptBuilder() diff --git a/services/ai-core-services/src/parsing/prompt_builder.py b/services/ai-core-services/src/parsing/prompt_builder.py new file mode 100644 index 0000000..e82dd56 --- /dev/null +++ b/services/ai-core-services/src/parsing/prompt_builder.py @@ -0,0 +1,205 @@ +RESUME_SCHEMA_JSON = """ +{ +"parsed_resume": { +"personal_info": { +"first_name": "string or null", +"last_name": "string or null", +"phone_number": "string or null", +"email": "string or null", +"linkedin_url": "string or null", +"github_url": "string or null", +"leetcode_url": "string or null" +}, +"primary_skills": ["string"], +"secondary_skills": ["string"], +"domain_expertise": ["string"], +"experience": { +"total_years_calculation": "string", +"total_years": "number or null", +"roles": [ +{ +"title": "string or null", +"company": "string or null", +"start_date": "string or null", +"end_date": "string or null", +"years": "number or null", +"highlights": ["string"] +} +] +}, +"skill_experience": [ +{ +"skill": "string", +"years": "number or null" +} +], +"education_certificates": [ +{ +"name": "string", +"issuer": "string or null", +"year": "string or null", +"type": "degree or certification" +} +] +} +} +""" + +RESUME_PROMPT_TEMPLATE = """ +You are an expert resume parser specializing in technology and business resumes. Extract ONLY information explicitly stated in the provided resume and return exactly one valid JSON object matching the schema below. + +CURRENT DATE: __CURRENT_DATE__ +Use this date as the reference date for all duration calculations involving "present". Do not use any other assumed current date. + +GENERAL RULES: + +* Output ONLY valid JSON. No markdown, explanations, comments, or extra text. +* Never guess, infer, fabricate, or fill missing information using common knowledge. +* If information is not explicitly available, use null for scalar fields and [] for arrays. +* Extract information from the entire resume, including the header, summary, skills, experience, projects, education, and certifications. +* Normalize and deduplicate equivalent skills while preserving their meaning. +* Do not infer a skill, technology, responsibility, industry, or achievement from a job title alone. + +OUTPUT FORMAT: + +* primary_skills MUST always be an array of strings, even if there is only one skill. +* secondary_skills MUST always be an array of strings, even if there is only one skill. +* domain_expertise MUST always be an array of strings, even if there is only one domain. +* roles MUST always be an array of objects, with one object per distinct work experience role. +* highlights MUST always be an array of strings for every role, even if there is only one highlight. +* education_certificates MUST always be an array of objects, even if there is only one degree or certification. +* Never return an array field as a single string, object, or null. +* If no information is available for an array field, return []. +* Do not omit any field defined in the schema. + +SKILLS: + +* primary_skills: Core technical/hard skills explicitly mentioned, including programming languages, frameworks, libraries, databases, cloud platforms, DevOps, infrastructure, APIs, testing, data technologies, and other technical tools. +* secondary_skills: Supporting technologies, tools, methodologies, soft skills, leadership skills, and less-central technical skills explicitly mentioned. +* domain_expertise: Explicitly stated industries or business domains, such as Finance, Banking, Healthcare, E-commerce, Logistics, Retail, SaaS, or Telecom. +* Do not place the same normalized skill in both primary_skills and secondary_skills. +* Do not infer skills from projects or job titles unless the skill is explicitly stated. +* Normalize obvious naming variations, e.g. "React.js"/"ReactJS" → "React", "Postgres"/"PostgreSQL" → "PostgreSQL". +* CRITICAL RULE: Completely ignore Internship roles. Do not extract any skills from internship descriptions into primary_skills or secondary_skills. + +SKILL-SPECIFIC EXPERIENCE (skill_experience): + +* For EVERY skill identified in primary_skills and secondary_skills, determine the candidate's total years of experience with that specific skill. +* STEP 1 - Calculate from ROLE HIGHLIGHTS for ALL skills: + - Look at each role in the "Professional Experience" / "Work Experience" section. + - A skill is considered "used in a role" ONLY if it is explicitly mentioned in that role's bullet points/highlights. + - Sum the durations (years) of all roles where the skill is explicitly mentioned. + - Subtract overlapping role durations to prevent double-counting. + - This provides the `calculated_role_years`. +* STEP 2 - Check for DIRECT per-skill year statements: + - A valid explicit statement is when the candidate directly associates a specific number of years with one or more specific skills, such as: "Java (5 years)", "7+ years of Python", or "10 years of experience in Java, Spring". + - If a candidate includes a broad professional summary statement like "3+ years of experience in enterprise development using Java, Spring Boot", apply that exact number of years (e.g., 3.0) to EACH of the skills listed. + - CRITICAL: If a candidate states a number of years in a professional summary paragraph (e.g., "7 years of success in DevOps..."), and then lists skills within that SAME summary paragraph (e.g., "... Skilled in Jenkins, Docker"), you MUST apply that exact number of years (e.g., 7.0) to ALL skills mentioned anywhere within that summary block, even if they are in separate sentences. + - This provides the `stated_years`. +* STEP 3 - Determine Final Skill Experience: + - If a skill has both `stated_years` and `calculated_role_years`, you MUST use the MAXIMUM of the two values. (e.g., if summary says 3 years, but roles add up to 4.5 years, use 4.5 years). + - If a skill only has one of the values, use that value. +* Round the final skill experience to 1 decimal place. +* If a skill appears ONLY in a "Technical Skills" section or summary but is NOT mentioned in any role highlight or project, assign it 0.0 years. + +WORK EXPERIENCE: + +* Extract every distinct professional role separately. +* CRITICAL RULE - INTERNSHIPS: If a role title or description contains the word "Intern" or "Internship", you MUST skip it entirely. DO NOT extract it. DO NOT add its duration to `total_years`. DO NOT add its duration to any skill. Treat the internship as if it does not exist. +* For every role, extract title, company, start_date, end_date, years, and highlights. +* Extract dates only from information explicitly associated with that role. +* If a date is unavailable, use null. Never infer a missing date from another role. +* If the role is explicitly ongoing/current, end_date must be "present". +* Date format: + * YYYY-MM-DD when exact date is explicitly available. + * YYYY-MM when month and year are explicitly available. + * YYYY when only the year is explicitly available. +* Calculate years from the extracted start_date and end_date. +* If both month and year are available, calculate the exact elapsed duration using those months. +* If only years are available, calculate duration using the difference between the years. For example, 2022–2024 = 2.00 years. +* If only a start year is available and the role is ongoing, calculate from January 1 of that year through CURRENT DATE. +* If start month/year is available and the role is ongoing, calculate from the first day of that month through CURRENT DATE. +* If an end month/year is available, calculate through the end of that month. +* If an exact day is available, use the exact day. +* Calculate years as elapsed days / 365.25 when exact or month-level dates are available, and round to 1 decimal place. +* For year-only ranges, use the year difference directly and round to 1 decimal place. +* Do not return years as null merely because only a year or month/year is available. +* Return years as null only when the available dates are genuinely insufficient to calculate a reliable duration. +* total_years must represent unique professional experience across all extracted roles. Overlapping employment periods must not be double-counted. +* CRITICAL RULE FOR MATH: Inside the `experience` object, you MUST first provide a string field called `total_years_calculation`. In this field, you must write out the exact mathematical addition of the individual role `years` (e.g., "7.5 + 0.6 = 8.1"). Subtract any overlapping durations. +* After `total_years_calculation`, provide `total_years` as the final calculated float exactly matching your calculation. Do not guess. +* `total_years` and individual role `years` should be rounded and formatted to only 1 decimal place (e.g., 3.45 becomes 3.4). + +ROLE HIGHLIGHTS: + +* highlights must contain concise, factual information specifically associated with that role. +* Include role-specific technologies explicitly mentioned in that role, including programming languages, frameworks, libraries, databases, cloud, DevOps, CI/CD, APIs, monitoring, infrastructure, testing, and data tools. +* Include important responsibilities, projects, architectures, systems, and measurable achievements explicitly stated for that role. +* Preserve technology + context. For example: "Developed microservices using Java and Spring Boot" rather than only "Java". +* A technology may appear in both global skills and the relevant role's highlights. +* Associate a technology with a role ONLY when the resume explicitly connects it to that role's work, project, responsibility, or description. +* Do not copy technologies from another role into the current role. +* Do not copy the entire global skills section into every role. +* Do not infer technologies from the job title. For example, "DevOps Engineer" does not automatically mean AWS, Docker, Kubernetes, or Jenkins. +* Keep highlights concise and information-dense. +* If no role-specific details are available, return []. + +PERSONAL INFORMATION: + +* Extract first_name, last_name, phone_number, email, linkedin_url, github_url, and leetcode_url only when explicitly present. +* Do not expand initials into full names. +* Do not infer missing contact information. +* Preserve explicitly provided contact information accurately. + +NAME PARSING: + +* Extract first_name and last_name based on the person's actual given name and family/surname, not simply by word position. +* Do not assume the first word is the first name or that the last word is the last name. +* If the resume does not provide enough reliable information to determine the first name and last name, use null rather than guessing. + +EDUCATION AND CERTIFICATIONS: + +* Extract every explicitly stated degree and certification. +* type must be exactly "degree" or "certification". +* name must contain the explicitly stated degree or certification name. +* issuer must contain the explicitly stated university, institution, or certification issuer when available. +* year must contain the explicitly stated graduation, completion, or certification year when available. +* Never infer an issuer or year. + +FINAL VALIDATION: + +Before returning the JSON, verify that: + +* Every schema field is present, even when its value is null or []. +* Every extracted role has a title, company, start_date, end_date, years, and highlights field. +* Ongoing roles use "present" as end_date. +* Calculated years are numeric or null, never strings. +* primary_skills, secondary_skills, domain_expertise, roles, highlights, and education_certificates are always arrays as defined in the schema. +* Empty array fields contain [] rather than null. +* Skills are deduplicated and normalized. +* Role highlights contain only information associated with that role. +* No technology was inferred solely from a job title. +* No missing information was guessed. +* The output exactly matches the provided schema. +* The output is valid JSON. + +SCHEMA: +__RESUME_SCHEMA__ + +RESUME TEXT: +__RESUME_TEXT__ +""" + + +class ResumePromptBuilder: + """Builds the LLM prompt for resume parsing.""" + + def build(self, markdown_text: str, current_date: str) -> str: + prompt = RESUME_PROMPT_TEMPLATE + prompt = prompt.replace("__CURRENT_DATE__", current_date) + prompt = prompt.replace("__RESUME_SCHEMA__", RESUME_SCHEMA_JSON) + prompt = prompt.replace("__RESUME_TEXT__", markdown_text) + return prompt + + +resume_prompt_builder = ResumePromptBuilder() diff --git a/services/ai-core-services/src/parsing/resume_parser.py b/services/ai-core-services/src/parsing/resume_parser.py index a8a0c19..4a16ffc 100644 --- a/services/ai-core-services/src/parsing/resume_parser.py +++ b/services/ai-core-services/src/parsing/resume_parser.py @@ -1,313 +1,56 @@ import asyncio import json -import datetime -import calendar +import os +from datetime import datetime +from typing import Optional + +from src.common.llm_utils import parse_llm_json +from src.core.logger import logger from src.core.storage import storage_client -from src.parsing.docling_wrapper import docling_wrapper from src.llm.client import OllamaClient -from src.core.logger import logger +from src.parsing.docling_wrapper import docling_wrapper +from src.parsing.experience_calculator import recalculate_experience +from src.parsing.prompt_builder import resume_prompt_builder # Restrict OCR to a single concurrent thread to prevent PyTorch OpenMP thread deadlocks, # while still offloading it to a background thread so the FastAPI event loop is NOT blocked! -# Restrict to 1 concurrent OCR extraction to prevent PyTorch CPU thread deadlocks ocr_semaphore = asyncio.Semaphore(1) + class ResumeParser: - def __init__(self): - self.llm_client = OllamaClient() + def __init__(self, llm_client: Optional[OllamaClient] = None): + self.llm_client = llm_client or OllamaClient() async def parse_s3_file(self, s3_key: str) -> dict: """Downloads from S3, extracts markdown, and parses using LLM.""" - # 1. Download file (Non-blocking) tmp_path = await asyncio.to_thread(storage_client.download_to_tempfile, s3_key) - + try: - # 2. Extract Text (Sequential to prevent PyTorch thread deadlocks, but non-blocking for FastAPI) async with ocr_semaphore: markdown_text = await asyncio.to_thread(docling_wrapper.extract_markdown, tmp_path) - - # 3. Build Prompt - current_date = datetime.datetime.now().strftime("%Y-%m-%d") - prompt = self._build_prompt(markdown_text, current_date) - - # 4. Call LLM (using the OpenAI chat endpoint to strictly enforce JSON) + + current_date = datetime.now().strftime("%Y-%m-%d") + prompt = resume_prompt_builder.build(markdown_text, current_date) + logger.info("Sending resume extraction prompt to LLM") - response = await self.llm_client.openai_chat_generate(prompt=prompt, temperature=0.1, timeout=120.0) - - # 5. Parse JSON Response - raw_json = response.response.strip() + response = await self.llm_client.openai_chat_generate( + prompt=prompt, temperature=0.1, timeout=120.0 + ) + try: - # Basic cleanup in case LLM wraps it in markdown blocks - if raw_json.startswith("```json"): - raw_json = raw_json[7:] - if raw_json.startswith("```"): - raw_json = raw_json[3:] - if raw_json.endswith("```"): - raw_json = raw_json[:-3] - - parsed_data = json.loads(raw_json.strip()) - if "parsed_resume" in parsed_data: + parsed_data = parse_llm_json(response.response) + if isinstance(parsed_data, dict) and "parsed_resume" in parsed_data: parsed_data = parsed_data["parsed_resume"] - - self._recalculate_experience(parsed_data) + + recalculate_experience(parsed_data) return parsed_data except json.JSONDecodeError as e: logger.error(f"Failed to parse LLM JSON output: {e}\nRaw Output: {response.response}") - raise ValueError("LLM returned invalid JSON") - + raise ValueError("LLM returned invalid JSON") from e + finally: - import os if os.path.exists(tmp_path): os.remove(tmp_path) - def _recalculate_experience(self, parsed_data: dict) -> None: - """Overrides the LLM's hallucinated date math with strict Python datetime calculations.""" - if not parsed_data or "experience" not in parsed_data: - return - - roles = parsed_data["experience"].get("roles", []) - if not roles: - return - - def parse_date(date_str, is_end_date=False): - if not date_str or str(date_str).lower() in ("present", "current", "now", "null", "none"): - return datetime.datetime.now() - date_str = str(date_str).strip() - try: - if len(date_str) == 10: - return datetime.datetime.strptime(date_str, "%Y-%m-%d") - elif len(date_str) == 7: - dt = datetime.datetime.strptime(date_str, "%Y-%m") - if is_end_date: - _, last_day = calendar.monthrange(dt.year, dt.month) - dt = dt.replace(day=last_day) - return dt - elif len(date_str) == 4: - dt = datetime.datetime.strptime(date_str, "%Y") - if is_end_date: - dt = dt.replace(month=12, day=31) - return dt - except ValueError: - pass - return None - - intervals = [] - for role in roles: - start_dt = parse_date(role.get("start_date"), is_end_date=False) - end_dt = parse_date(role.get("end_date"), is_end_date=True) - - if start_dt and end_dt and start_dt <= end_dt: - days = (end_dt - start_dt).days - role["years"] = round(days / 365.25, 1) - intervals.append([start_dt, end_dt]) - else: - role["years"] = 0.0 - - # Merge overlapping intervals for total_years - if not intervals: - parsed_data["experience"]["total_years"] = 0.0 - return - - intervals.sort(key=lambda x: x[0]) - merged = [intervals[0]] - for current in intervals[1:]: - last = merged[-1] - if current[0] <= last[1]: - last[1] = max(last[1], current[1]) - else: - merged.append(current) - - total_days = sum((iv[1] - iv[0]).days for iv in merged) - parsed_data["experience"]["total_years"] = round(total_days / 365.25, 1) - - def _build_prompt(self, markdown_text: str, current_date: str) -> str: - return f""" -You are an expert resume parser specializing in technology and business resumes. Extract ONLY information explicitly stated in the provided resume and return exactly one valid JSON object matching the schema below. - -CURRENT DATE: {current_date} -Use this date as the reference date for all duration calculations involving "present". Do not use any other assumed current date. - -GENERAL RULES: - -* Output ONLY valid JSON. No markdown, explanations, comments, or extra text. -* Never guess, infer, fabricate, or fill missing information using common knowledge. -* If information is not explicitly available, use null for scalar fields and [] for arrays. -* Extract information from the entire resume, including the header, summary, skills, experience, projects, education, and certifications. -* Normalize and deduplicate equivalent skills while preserving their meaning. -* Do not infer a skill, technology, responsibility, industry, or achievement from a job title alone. - -OUTPUT FORMAT: - -* primary_skills MUST always be an array of strings, even if there is only one skill. -* secondary_skills MUST always be an array of strings, even if there is only one skill. -* domain_expertise MUST always be an array of strings, even if there is only one domain. -* roles MUST always be an array of objects, with one object per distinct work experience role. -* highlights MUST always be an array of strings for every role, even if there is only one highlight. -* education_certificates MUST always be an array of objects, even if there is only one degree or certification. -* Never return an array field as a single string, object, or null. -* If no information is available for an array field, return []. -* Do not omit any field defined in the schema. - -SKILLS: - -* primary_skills: Core technical/hard skills explicitly mentioned, including programming languages, frameworks, libraries, databases, cloud platforms, DevOps, infrastructure, APIs, testing, data technologies, and other technical tools. -* secondary_skills: Supporting technologies, tools, methodologies, soft skills, leadership skills, and less-central technical skills explicitly mentioned. -* domain_expertise: Explicitly stated industries or business domains, such as Finance, Banking, Healthcare, E-commerce, Logistics, Retail, SaaS, or Telecom. -* Do not place the same normalized skill in both primary_skills and secondary_skills. -* Do not infer skills from projects or job titles unless the skill is explicitly stated. -* Normalize obvious naming variations, e.g. "React.js"/"ReactJS" → "React", "Postgres"/"PostgreSQL" → "PostgreSQL". -* CRITICAL RULE: Completely ignore Internship roles. Do not extract any skills from internship descriptions into primary_skills or secondary_skills. - -SKILL-SPECIFIC EXPERIENCE (skill_experience): - -* For EVERY skill identified in primary_skills and secondary_skills, determine the candidate's total years of experience with that specific skill. -* STEP 1 - Calculate from ROLE HIGHLIGHTS for ALL skills: - - Look at each role in the "Professional Experience" / "Work Experience" section. - - A skill is considered "used in a role" ONLY if it is explicitly mentioned in that role's bullet points/highlights. - - Sum the durations (years) of all roles where the skill is explicitly mentioned. - - Subtract overlapping role durations to prevent double-counting. - - This provides the `calculated_role_years`. -* STEP 2 - Check for DIRECT per-skill year statements: - - A valid explicit statement is when the candidate directly associates a specific number of years with one or more specific skills, such as: "Java (5 years)", "7+ years of Python", or "10 years of experience in Java, Spring". - - If a candidate includes a broad professional summary statement like "3+ years of experience in enterprise development using Java, Spring Boot", apply that exact number of years (e.g., 3.0) to EACH of the skills listed. - - CRITICAL: If a candidate states a number of years in a professional summary paragraph (e.g., "7 years of success in DevOps..."), and then lists skills within that SAME summary paragraph (e.g., "... Skilled in Jenkins, Docker"), you MUST apply that exact number of years (e.g., 7.0) to ALL skills mentioned anywhere within that summary block, even if they are in separate sentences. - - This provides the `stated_years`. -* STEP 3 - Determine Final Skill Experience: - - If a skill has both `stated_years` and `calculated_role_years`, you MUST use the MAXIMUM of the two values. (e.g., if summary says 3 years, but roles add up to 4.5 years, use 4.5 years). - - If a skill only has one of the values, use that value. -* Round the final skill experience to 1 decimal place. -* If a skill appears ONLY in a "Technical Skills" section or summary but is NOT mentioned in any role highlight or project, assign it 0.0 years. - -WORK EXPERIENCE: - -* Extract every distinct professional role separately. -* CRITICAL RULE - INTERNSHIPS: If a role title or description contains the word "Intern" or "Internship", you MUST skip it entirely. DO NOT extract it. DO NOT add its duration to `total_years`. DO NOT add its duration to any skill. Treat the internship as if it does not exist. -* For every role, extract title, company, start_date, end_date, years, and highlights. -* Extract dates only from information explicitly associated with that role. -* If a date is unavailable, use null. Never infer a missing date from another role. -* If the role is explicitly ongoing/current, end_date must be "present". -* Date format: - * YYYY-MM-DD when exact date is explicitly available. - * YYYY-MM when month and year are explicitly available. - * YYYY when only the year is explicitly available. -* Calculate years from the extracted start_date and end_date. -* If both month and year are available, calculate the exact elapsed duration using those months. -* If only years are available, calculate duration using the difference between the years. For example, 2022–2024 = 2.00 years. -* If only a start year is available and the role is ongoing, calculate from January 1 of that year through CURRENT DATE. -* If start month/year is available and the role is ongoing, calculate from the first day of that month through CURRENT DATE. -* If an end month/year is available, calculate through the end of that month. -* If an exact day is available, use the exact day. -* Calculate years as elapsed days / 365.25 when exact or month-level dates are available, and round to 1 decimal place. -* For year-only ranges, use the year difference directly and round to 1 decimal place. -* Do not return years as null merely because only a year or month/year is available. -* Return years as null only when the available dates are genuinely insufficient to calculate a reliable duration. -* total_years must represent unique professional experience across all extracted roles. Overlapping employment periods must not be double-counted. -* CRITICAL RULE FOR MATH: Inside the `experience` object, you MUST first provide a string field called `total_years_calculation`. In this field, you must write out the exact mathematical addition of the individual role `years` (e.g., "7.5 + 0.6 = 8.1"). Subtract any overlapping durations. -* After `total_years_calculation`, provide `total_years` as the final calculated float exactly matching your calculation. Do not guess. -* `total_years` and individual role `years` should be rounded and formatted to only 1 decimal place (e.g., 3.45 becomes 3.4). - -ROLE HIGHLIGHTS: - -* highlights must contain concise, factual information specifically associated with that role. -* Include role-specific technologies explicitly mentioned in that role, including programming languages, frameworks, libraries, databases, cloud, DevOps, CI/CD, APIs, monitoring, infrastructure, testing, and data tools. -* Include important responsibilities, projects, architectures, systems, and measurable achievements explicitly stated for that role. -* Preserve technology + context. For example: "Developed microservices using Java and Spring Boot" rather than only "Java". -* A technology may appear in both global skills and the relevant role's highlights. -* Associate a technology with a role ONLY when the resume explicitly connects it to that role's work, project, responsibility, or description. -* Do not copy technologies from another role into the current role. -* Do not copy the entire global skills section into every role. -* Do not infer technologies from the job title. For example, "DevOps Engineer" does not automatically mean AWS, Docker, Kubernetes, or Jenkins. -* Keep highlights concise and information-dense. -* If no role-specific details are available, return []. - -PERSONAL INFORMATION: - -* Extract first_name, last_name, phone_number, email, linkedin_url, github_url, and leetcode_url only when explicitly present. -* Do not expand initials into full names. -* Do not infer missing contact information. -* Preserve explicitly provided contact information accurately. - -NAME PARSING: - -* Extract first_name and last_name based on the person's actual given name and family/surname, not simply by word position. -* Do not assume the first word is the first name or that the last word is the last name. -* If the resume does not provide enough reliable information to determine the first name and last name, use null rather than guessing. - -EDUCATION AND CERTIFICATIONS: - -* Extract every explicitly stated degree and certification. -* type must be exactly "degree" or "certification". -* name must contain the explicitly stated degree or certification name. -* issuer must contain the explicitly stated university, institution, or certification issuer when available. -* year must contain the explicitly stated graduation, completion, or certification year when available. -* Never infer an issuer or year. - -FINAL VALIDATION: - -Before returning the JSON, verify that: - -* Every schema field is present, even when its value is null or []. -* Every extracted role has a title, company, start_date, end_date, years, and highlights field. -* Ongoing roles use "present" as end_date. -* Calculated years are numeric or null, never strings. -* primary_skills, secondary_skills, domain_expertise, roles, highlights, and education_certificates are always arrays as defined in the schema. -* Empty array fields contain [] rather than null. -* Skills are deduplicated and normalized. -* Role highlights contain only information associated with that role. -* No technology was inferred solely from a job title. -* No missing information was guessed. -* The output exactly matches the provided schema. -* The output is valid JSON. - -SCHEMA: -""" + """ -{ -"parsed_resume": { -"personal_info": { -"first_name": "string or null", -"last_name": "string or null", -"phone_number": "string or null", -"email": "string or null", -"linkedin_url": "string or null", -"github_url": "string or null", -"leetcode_url": "string or null" -}, -"primary_skills": ["string"], -"secondary_skills": ["string"], -"domain_expertise": ["string"], -"experience": { -"total_years_calculation": "string", -"total_years": "number or null", -"roles": [ -{ -"title": "string or null", -"company": "string or null", -"start_date": "string or null", -"end_date": "string or null", -"years": "number or null", -"highlights": ["string"] -} -] -}, -"skill_experience": [ -{ -"skill": "string", -"years": "number or null" -} -], -"education_certificates": [ -{ -"name": "string", -"issuer": "string or null", -"year": "string or null", -"type": "degree or certification" -} -] -} -} -""" + f""" -RESUME TEXT: -{markdown_text} -""" resume_parser = ResumeParser() diff --git a/services/ai-core-services/src/question_generation/__init__.py b/services/ai-core-services/src/question_generation/__init__.py new file mode 100644 index 0000000..a8502b7 --- /dev/null +++ b/services/ai-core-services/src/question_generation/__init__.py @@ -0,0 +1,11 @@ +from src.question_generation.schemas import ( + GeneratedQuestion, + GenerateQuestionsRequest, + GenerateQuestionsResponse, +) + +__all__ = [ + "GeneratedQuestion", + "GenerateQuestionsRequest", + "GenerateQuestionsResponse", +] diff --git a/services/ai-core-services/src/question_generation/generator.py b/services/ai-core-services/src/question_generation/generator.py index 1dc4f77..7843c67 100644 --- a/services/ai-core-services/src/question_generation/generator.py +++ b/services/ai-core-services/src/question_generation/generator.py @@ -1,4 +1,6 @@ import json +from typing import Any + from src.llm.client import OllamaClient from src.core.logger import logger from src.question_generation.schemas import ( @@ -7,86 +9,69 @@ GeneratedQuestion, ) from src.question_generation.prompt_builder import question_prompt_builder +from src.question_generation.match_context import neutral_job_fit_analysis from src.common.llm_utils import parse_llm_json -class QuestionGenerator: - """Orchestrates the question generation pipeline. +def _parse_questions(raw: Any) -> list[GeneratedQuestion]: + parsed_questions = parse_llm_json(raw) if isinstance(raw, str) else raw + + if isinstance(parsed_questions, dict): + for key in ("questions", "generated_questions", "data"): + if key in parsed_questions and isinstance(parsed_questions[key], list): + parsed_questions = parsed_questions[key] + break - 1. Builds the prompt from parsed_jd and job_fit_analysis - 2. Calls the LLM via OpenAI-compatible chat endpoint - 3. Parses the JSON array response - 4. Returns the questions to Core API (no DB write) - """ + if not isinstance(parsed_questions, list): + raise ValueError(f"Expected JSON array, got {type(parsed_questions).__name__}") + + return [GeneratedQuestion(**q) for q in parsed_questions] + + +class QuestionGenerator: + """Orchestrates the question generation pipeline.""" def __init__(self): self.llm_client = OllamaClient() + async def _call_llm(self, prompt: str, *, log_extra: dict[str, Any]) -> list[GeneratedQuestion]: + logger.info("Sending question generation prompt to LLM", extra=log_extra) + response = await self.llm_client.openai_chat_generate( + prompt=prompt, temperature=0.3, timeout=120.0 + ) + return _parse_questions(response.response) + async def generate(self, request: GenerateQuestionsRequest) -> GenerateQuestionsResponse: - """Generate tailored interview questions for a candidate.""" + """Generate tailored interview questions for a candidate session or job bank.""" + log_extra = {"interview_session_id": request.interview_session_id} try: - # 1. Build prompt + match_result = request.job_fit_analysis or neutral_job_fit_analysis( + request.parsed_jd + ) prompt = question_prompt_builder.build( parsed_jd=request.parsed_jd, - match_result=request.job_fit_analysis, - ) - - # 2. Call LLM - logger.info( - "Sending question generation prompt to LLM", - extra={"interview_session_id": request.interview_session_id}, - ) - response = await self.llm_client.openai_chat_generate( - prompt=prompt, temperature=0.3, timeout=120.0 + match_result=match_result, ) - - # 3. Parse JSON array response - parsed_questions = parse_llm_json(response.response) - - # Handle case where LLM wraps array in an object - if isinstance(parsed_questions, dict): - # Try common wrapper keys - for key in ("questions", "generated_questions", "data"): - if key in parsed_questions and isinstance(parsed_questions[key], list): - parsed_questions = parsed_questions[key] - break - - if not isinstance(parsed_questions, list): - raise ValueError(f"Expected JSON array, got {type(parsed_questions).__name__}") - - # 4. Validate each question against the schema - questions = [GeneratedQuestion(**q) for q in parsed_questions] - + questions = await self._call_llm(prompt, log_extra=log_extra) logger.info( "Successfully generated interview questions", - extra={ - "interview_session_id": request.interview_session_id, - "count": len(questions), - }, + extra={**log_extra, "count": len(questions)}, ) - return GenerateQuestionsResponse( interview_session_id=request.interview_session_id, status="success", questions=questions, count=len(questions), ) - except json.JSONDecodeError as e: - logger.error( - f"Failed to parse question generation JSON output: {e}", - extra={"interview_session_id": request.interview_session_id}, - ) + logger.error(f"Failed to parse question generation JSON output: {e}", extra=log_extra) return GenerateQuestionsResponse( interview_session_id=request.interview_session_id, status="error", error_message=f"LLM returned invalid JSON: {str(e)}", ) except Exception as e: - logger.error( - f"Unexpected error during question generation: {e}", - extra={"interview_session_id": request.interview_session_id}, - ) + logger.error(f"Unexpected error during question generation: {e}", extra=log_extra) return GenerateQuestionsResponse( interview_session_id=request.interview_session_id, status="error", diff --git a/services/ai-core-services/src/question_generation/match_context.py b/services/ai-core-services/src/question_generation/match_context.py new file mode 100644 index 0000000..429977b --- /dev/null +++ b/services/ai-core-services/src/question_generation/match_context.py @@ -0,0 +1,38 @@ +"""Synthetic job-fit context for job-level question banks (no candidate resume).""" + +from __future__ import annotations + +from typing import Any + +from src.parsing.schemas import ParsedJDData + + +def neutral_job_fit_analysis(parsed_jd: ParsedJDData) -> dict[str, Any]: + """Build a JD-only match payload so dev's question prompt can run without a candidate.""" + must_have = [skill.skill for skill in parsed_jd.skills.must_have if skill.skill] + good_to_have = [skill.skill for skill in parsed_jd.skills.good_to_have if skill.skill] + + return { + "score_breakdown": { + "raw_must_have_skills": 0.0, + "raw_good_to_have_skills": 0.0, + "raw_experience": 0.0, + "raw_qualifications": 0.0, + "skills_score": 0.0, + "experience_score": 0.0, + "qualifications_score": 0.0, + }, + "match_score": 0.0, + "reasoning": [ + "Job-level screening bank: no candidate resume provided.", + "Generate role-based questions from the JD must-have skills and responsibilities.", + ], + "strengths": [], + "concerns": must_have[:4] or ["Assess core must-have skills for this role."], + "matched_skills": {"must_have": must_have, "good_to_have": []}, + "missing_skills": {"must_have": [], "good_to_have": good_to_have}, + "must_have_experience": [], + "good_to_have_experience": [], + "qualification_match": False, + "experience_match": False, + } diff --git a/services/ai-core-services/src/question_generation/prompt_builder.py b/services/ai-core-services/src/question_generation/prompt_builder.py index 717d794..40e5b84 100644 --- a/services/ai-core-services/src/question_generation/prompt_builder.py +++ b/services/ai-core-services/src/question_generation/prompt_builder.py @@ -20,8 +20,6 @@ def build(self, parsed_jd: ParsedJDData, match_result: dict) -> str: must_have_skills = json.dumps([s.model_dump() for s in parsed_jd.skills.must_have]) good_to_have_skills = json.dumps([s.model_dump() for s in parsed_jd.skills.good_to_have]) responsibilities = json.dumps(parsed_jd.responsibilities) - min_y = parsed_jd.experience_required.min_years or 0 - # Full match JSON match_json = json.dumps(match_result, indent=2) @@ -75,7 +73,7 @@ def build(self, parsed_jd: ParsedJDData, match_result: dict) -> str: - Use "partial_depth" if the Target Experience Level is 2-4 years. - Use "full_depth" if the Target Experience Level is 5+ years. -═══ OUTPUT FORMAT ═══i +═══ OUTPUT FORMAT ═══ Return a JSON array only. No markdown, no commentary. [ {{ diff --git a/services/ai-core-services/src/question_generation/schemas.py b/services/ai-core-services/src/question_generation/schemas.py index ff084ce..768e957 100644 --- a/services/ai-core-services/src/question_generation/schemas.py +++ b/services/ai-core-services/src/question_generation/schemas.py @@ -4,9 +4,15 @@ class GenerateQuestionsRequest(BaseModel): - interview_session_id: str = Field(..., description="UUID of the interview session") + interview_session_id: str = Field( + ..., + description="UUID of the interview session, or job id for job-level banks", + ) parsed_jd: ParsedJDData = Field(..., description="Parsed JD JSON from Pipeline A") - job_fit_analysis: Dict[str, Any] = Field(..., description="Full match analysis JSON from the matching endpoint") + job_fit_analysis: Dict[str, Any] | None = Field( + default=None, + description="Full match analysis JSON from matching; omitted for job-level banks", + ) class GeneratedQuestion(BaseModel): @@ -19,7 +25,7 @@ class GeneratedQuestion(BaseModel): class GenerateQuestionsResponse(BaseModel): - interview_session_id: str + interview_session_id: str | None = None status: str = "success" questions: Optional[List[GeneratedQuestion]] = None count: int = 0 diff --git a/services/ai-core-services/tests/__init__.py b/services/ai-core-services/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/tests/api/__init__.py b/services/ai-core-services/tests/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/tests/api/v1/__init__.py b/services/ai-core-services/tests/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/tests/api/v1/conftest.py b/services/ai-core-services/tests/api/v1/conftest.py new file mode 100644 index 0000000..99b5015 --- /dev/null +++ b/services/ai-core-services/tests/api/v1/conftest.py @@ -0,0 +1,11 @@ +import pytest +from httpx import ASGITransport, AsyncClient + + +@pytest.fixture +async def client(): + from src.main import app + + transport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://test") as ac: + yield ac diff --git a/services/ai-core-services/tests/api/v1/test_matching.py b/services/ai-core-services/tests/api/v1/test_matching.py new file mode 100644 index 0000000..9139227 --- /dev/null +++ b/services/ai-core-services/tests/api/v1/test_matching.py @@ -0,0 +1,74 @@ +from unittest.mock import AsyncMock, patch + +import pytest + +from src.parsing.schemas import ParsedJDData, ParsedResumeData + + +@pytest.mark.asyncio +async def test_match_endpoint_success(client): + mock_result = { + "score_breakdown": { + "raw_must_have_skills": 40.0, + "raw_good_to_have_skills": 20.0, + "raw_experience": 30.0, + "raw_qualifications": 10.0, + "skills_score": 10.0, + "experience_score": 10.0, + "qualifications_score": 10.0, + }, + "match_score": 10.0, + "reasoning": ["Strong fit."], + "strengths": ["Python"], + "concerns": ["No major concerns identified."], + "matched_skills": {"must_have": ["Python"], "good_to_have": []}, + "missing_skills": {"must_have": [], "good_to_have": []}, + "must_have_experience": [], + "good_to_have_experience": [], + "qualification_match": True, + "experience_match": True, + } + + with patch( + "src.api.v1.matching.job_fit_matcher.analyze_fit", + new_callable=AsyncMock, + return_value=mock_result, + ): + response = await client.post( + "/internal/v1/match/resume-jd", + json={ + "application_id": "app-1", + "job_id": "job-1", + "parsed_resume": ParsedResumeData(primary_skills=["Python"]).model_dump(), + "parsed_jd": ParsedJDData(title="Engineer").model_dump(), + }, + ) + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "success" + assert body["application_id"] == "app-1" + assert body["job_fit_analysis"]["match_score"] == 10.0 + + +@pytest.mark.asyncio +async def test_match_endpoint_error(client): + with patch( + "src.api.v1.matching.job_fit_matcher.analyze_fit", + new_callable=AsyncMock, + side_effect=ValueError("LLM returned invalid matching JSON"), + ): + response = await client.post( + "/internal/v1/match/resume-jd", + json={ + "application_id": "app-2", + "job_id": "job-2", + "parsed_resume": ParsedResumeData().model_dump(), + "parsed_jd": ParsedJDData().model_dump(), + }, + ) + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "error" + assert "invalid matching JSON" in body["error_message"] diff --git a/services/ai-core-services/tests/conftest.py b/services/ai-core-services/tests/conftest.py new file mode 100644 index 0000000..5126a9f --- /dev/null +++ b/services/ai-core-services/tests/conftest.py @@ -0,0 +1 @@ +"""Shared pytest configuration for ai-core-services.""" diff --git a/services/ai-core-services/tests/job_fit_analysis/__init__.py b/services/ai-core-services/tests/job_fit_analysis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/tests/job_fit_analysis/test_score_calculator.py b/services/ai-core-services/tests/job_fit_analysis/test_score_calculator.py new file mode 100644 index 0000000..d8b92ab --- /dev/null +++ b/services/ai-core-services/tests/job_fit_analysis/test_score_calculator.py @@ -0,0 +1,87 @@ +from src.job_fit_analysis.score_calculator import recalculate_scores + + +def _base_match_result( + matched_must_have=None, + matched_good_to_have=None, + must_have_exp=None, + good_to_have_exp=None, + raw_qualifications=10.0, +): + return { + "score_breakdown": {"raw_qualifications": raw_qualifications}, + "matched_skills": { + "must_have": matched_must_have or ["Java", "Python"], + "good_to_have": matched_good_to_have or ["Docker"], + }, + "missing_skills": {"must_have": [], "good_to_have": []}, + "must_have_experience": must_have_exp + or [ + {"skill": "Java", "skill_experience_ratio": 1.0}, + {"skill": "Python", "skill_experience_ratio": 0.5}, + ], + "good_to_have_experience": good_to_have_exp + or [{"skill": "Docker", "skill_experience_ratio": 1.0}], + } + + +def test_recalculate_scores_perfect_match(): + result = _base_match_result( + matched_must_have=["Java", "Python"], + matched_good_to_have=["Docker"], + must_have_exp=[ + {"skill": "Java", "skill_experience_ratio": 1.0}, + {"skill": "Python", "skill_experience_ratio": 1.0}, + ], + ) + + recalculate_scores(result) + + assert result["score_breakdown"]["raw_must_have_skills"] == 40.0 + assert result["score_breakdown"]["raw_good_to_have_skills"] == 20.0 + assert result["score_breakdown"]["raw_experience"] == 30.0 + assert result["match_score"] == 10.0 + assert result["experience_match"] is True + + +def test_recalculate_scores_partial_must_have_skills(): + result = _base_match_result( + matched_must_have=["Java"], + matched_good_to_have=[], + must_have_exp=[ + {"skill": "Java", "skill_experience_ratio": 1.0}, + {"skill": "Python", "skill_experience_ratio": 0.0}, + ], + good_to_have_exp=[], + ) + + recalculate_scores(result) + + assert result["score_breakdown"]["raw_must_have_skills"] == 20.0 + assert result["score_breakdown"]["raw_good_to_have_skills"] == 20.0 + assert result["score_breakdown"]["raw_experience"] == 20.0 + assert result["experience_match"] is True + + +def test_recalculate_scores_experience_below_threshold(): + result = _base_match_result( + must_have_exp=[ + {"skill": "Java", "skill_experience_ratio": 0.25}, + {"skill": "Python", "skill_experience_ratio": 0.25}, + ], + good_to_have_exp=[{"skill": "Docker", "skill_experience_ratio": 0.0}], + ) + + recalculate_scores(result) + + assert result["score_breakdown"]["raw_experience"] == 5.0 + assert result["experience_match"] is False + + +def test_recalculate_scores_creates_score_breakdown_if_missing(): + result = {"matched_skills": {"must_have": [], "good_to_have": []}} + + recalculate_scores(result) + + assert "score_breakdown" in result + assert "match_score" in result diff --git a/services/ai-core-services/tests/parsing/__init__.py b/services/ai-core-services/tests/parsing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/tests/parsing/test_experience_calculator.py b/services/ai-core-services/tests/parsing/test_experience_calculator.py new file mode 100644 index 0000000..40596ab --- /dev/null +++ b/services/ai-core-services/tests/parsing/test_experience_calculator.py @@ -0,0 +1,51 @@ +from src.parsing.experience_calculator import recalculate_experience + + +def test_recalculate_experience_computes_role_years(): + parsed = { + "experience": { + "roles": [ + {"start_date": "2020-01-01", "end_date": "2022-01-01"}, + ] + } + } + + recalculate_experience(parsed) + + assert parsed["experience"]["roles"][0]["years"] == 2.0 + assert parsed["experience"]["total_years"] == 2.0 + + +def test_recalculate_experience_merges_overlapping_intervals(): + parsed = { + "experience": { + "roles": [ + {"start_date": "2020-01-01", "end_date": "2021-01-01"}, + {"start_date": "2020-06-01", "end_date": "2022-01-01"}, + ] + } + } + + recalculate_experience(parsed) + + assert parsed["experience"]["total_years"] == 2.0 + + +def test_recalculate_experience_present_end_date(): + parsed = { + "experience": { + "roles": [ + {"start_date": "2020-01-01", "end_date": "present"}, + ] + } + } + + recalculate_experience(parsed) + + assert parsed["experience"]["roles"][0]["years"] > 0 + + +def test_recalculate_experience_no_op_on_empty(): + parsed: dict = {} + recalculate_experience(parsed) + assert parsed == {} diff --git a/services/ai-core-services/tests/parsing/test_schemas.py b/services/ai-core-services/tests/parsing/test_schemas.py new file mode 100644 index 0000000..abaacfd --- /dev/null +++ b/services/ai-core-services/tests/parsing/test_schemas.py @@ -0,0 +1,29 @@ +import pytest +from pydantic import ValidationError + +from src.parsing.schemas import ParsedJDData, ParsedResumeData, SkillRequirement + + +def test_parsed_resume_data_defaults(): + resume = ParsedResumeData() + assert resume.primary_skills == [] + assert resume.secondary_skills == [] + assert resume.skill_experience == [] + + +def test_parsed_jd_data_with_skills(): + jd = ParsedJDData( + title="Backend Engineer", + skills={ + "must_have": [SkillRequirement(skill="Python", required_years=3.0)], + "good_to_have": [SkillRequirement(skill="Docker")], + }, + ) + assert jd.title == "Backend Engineer" + assert len(jd.skills.must_have) == 1 + assert jd.skills.must_have[0].required_years == 3.0 + + +def test_match_request_requires_parsed_payloads(): + with pytest.raises(ValidationError): + ParsedResumeData.model_validate({"primary_skills": "not-a-list"}) diff --git a/services/ai-core-services/uv.lock b/services/ai-core-services/uv.lock new file mode 100644 index 0000000..6144a0d --- /dev/null +++ b/services/ai-core-services/uv.lock @@ -0,0 +1,3547 @@ +version = 1 +requires-python = ">=3.11" +resolution-markers = [ + "sys_platform == 'darwin'", + "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten')", + "platform_system != 'Linux' and sys_platform == 'win32'", + "python_full_version < '3.12' and platform_system != 'Linux' and sys_platform == 'emscripten'", + "platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and platform_system == 'Linux' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and platform_system == 'Linux' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and platform_system != 'Linux' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and platform_system != 'Linux' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and platform_system == 'Linux' and sys_platform == 'emscripten'", +] + +[[package]] +name = "accelerate" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "packaging", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "psutil", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pyyaml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "safetensors", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "torch", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/75/94cd5d389649578aca399e5aa822637eec18319a1dadc400ffe2f9a7493f/accelerate-1.14.0.tar.gz", hash = "sha256:41b9c4377a54e0b460a959b0defa1b736e4ca0a2373252d9a539964c2afe3c8d", size = 412167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/db/253133d7e7cb40d3af384bb2f5c0b4a2b7fdcffbc95c688cc67a20a3c103/accelerate-1.14.0-py3-none-any.whl", hash = "sha256:e94390c2863b873be18f623f9df48a0d8fe5eff13ea7f1a00092b0a7904888c6", size = 389246 }, +] + +[[package]] +name = "ai-core-services" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "boto3", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "docling", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "fastapi", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "httpx", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "openai", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "psycopg2-binary", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic-settings", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "python-json-logger", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pyyaml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "sqlalchemy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "uvicorn", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "websockets", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "httpx", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pytest", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pytest-asyncio", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pytest-cov", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] + +[package.metadata] +requires-dist = [ + { name = "boto3", specifier = ">=1.34.0" }, + { name = "docling", specifier = ">=2.15.0" }, + { name = "fastapi", specifier = ">=0.100.0" }, + { name = "httpx", specifier = ">=0.24.1" }, + { name = "openai", specifier = ">=1.0.0" }, + { name = "psycopg2-binary", specifier = ">=2.9.6" }, + { name = "pydantic", specifier = ">=2.0.0" }, + { name = "pydantic-settings", specifier = ">=2.0.0" }, + { name = "python-json-logger", specifier = ">=2.0.7" }, + { name = "pyyaml", specifier = ">=6.0.0" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, + { name = "uvicorn", specifier = ">=0.22.0" }, + { name = "websockets", specifier = ">=11.0.3" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "httpx", specifier = ">=0.24" }, + { name = "pytest", specifier = ">=8.0" }, + { name = "pytest-asyncio", specifier = ">=0.23" }, + { name = "pytest-cov", specifier = ">=5.0" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302 }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427 }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034 } + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813 }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924 }, +] + +[[package]] +name = "boto3" +version = "1.43.82" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "jmespath", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "s3transfer", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/b1/5d8bc6b7335d86f15d43a34a4bbbeced202aef2b4e942c67a5846120c1ff/boto3-1.43.82.tar.gz", hash = "sha256:bc5a7824568c117110bac8fe7ccfac63f0a946f253953d42e73a8c1fb65162e0", size = 112671 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/d7/743fafc6d544b3552517d45958b093ec70b934b513d95b238abe935e54a3/boto3-1.43.82-py3-none-any.whl", hash = "sha256:65319e8bba6e30f74a6e2727a5688725222da2ab71c6069bc484ce5bfd101c73", size = 140026 }, +] + +[[package]] +name = "botocore" +version = "1.43.82" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "python-dateutil", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "urllib3", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/96/54e8d9a09689bf870bc723bf95ee8f5a4bba3b9c203baaaca3fb557d4924/botocore-1.43.82.tar.gz", hash = "sha256:347573c0bab52e29c923e28128764fcc50f469ed98dc5460220026cd3672ac0c", size = 16007302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/70/78cd83cf91d954846fe034edac415a700fd919782a003f1c71b779d06cfd/botocore-1.43.82-py3-none-any.whl", hash = "sha256:97b3e89061decc91e7745d726dae595cbe2053894611c49ea91d6fdcb2ecc36b", size = 15699695 }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585 }, + { url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189 }, + { url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724 }, + { url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078 }, + { url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650 }, + { url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325 }, + { url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140 }, + { url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791 }, + { url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730 }, + { url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791 }, + { url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598 }, + { url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217 }, + { url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417 }, + { url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774 }, + { url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653 }, + { url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630 }, + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456 }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530 }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200 }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222 }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951 }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801 }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070 }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110 }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836 }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712 }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977 }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207 }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562 }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507 }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551 }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700 }, + { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584 }, + { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359 }, + { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464 }, + { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676 }, + { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473 }, + { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156 }, + { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246 }, + { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660 }, + { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354 }, + { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638 }, + { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583 }, + { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038 }, + { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677 }, + { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491 }, + { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196 }, + { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660 }, + { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618 }, + { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362 }, + { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755 }, + { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295 }, + { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856 }, + { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318 }, + { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897 }, + { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848 }, + { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163 }, + { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823 }, + { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458 }, + { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717 }, + { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111 }, + { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128 }, + { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240 }, + { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282 }, + { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597 }, + { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376 }, + { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715 }, + { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848 }, + { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521 }, + { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054 }, + { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580 }, + { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325 }, + { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175 }, + { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123 }, + { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682 }, + { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826 }, + { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861 }, + { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758 }, + { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950 }, + { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329 }, + { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137 }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820 }, + { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504 }, + { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087 }, + { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269 }, + { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766 }, + { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814 }, + { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074 }, + { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476 }, + { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115 }, + { url = "https://files.pythonhosted.org/packages/4a/4e/8544831ef59d8f27ce92c80871380fdacc8076a8a56ed62f82e54f991333/charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af", size = 342048 }, + { url = "https://files.pythonhosted.org/packages/7f/a6/e3b46852424246065355644f4fb6dbccc0239a42a2eee27ecfc8957f0bcd/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8", size = 242997 }, + { url = "https://files.pythonhosted.org/packages/03/3b/0cc9a26777334ab2f2e3089b948bbf4e4fe72ea70b897715ef6415043ec8/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90", size = 237014 }, + { url = "https://files.pythonhosted.org/packages/8c/c2/027335f0aa337a2a2e121bac1ad88c4f02ba6053ea0926802784f3db11af/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20", size = 266174 }, + { url = "https://files.pythonhosted.org/packages/86/d3/e367787febe4e74769dec0f406f2c3c8d1b955fce5aee1fd0f94e8367a45/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449", size = 263361 }, + { url = "https://files.pythonhosted.org/packages/af/3d/391b193eb9f3e84b02f9314088c386debdc0debee843535aaea2e2c6715d/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a", size = 252143 }, + { url = "https://files.pythonhosted.org/packages/2e/57/de221f1745a90d418199761967e2776bfe2c275a1194220985e8c1d37833/charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0", size = 252086 }, + { url = "https://files.pythonhosted.org/packages/c8/e3/d119f86a01f9331e8186175f24873b1d74a7ee9e2e4b4d68f9947dae5afd/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e", size = 245231 }, + { url = "https://files.pythonhosted.org/packages/26/de/d8e48c135ae480879539cdb179c8d3b50c7879497d75dd899b5763b69cee/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2", size = 241546 }, + { url = "https://files.pythonhosted.org/packages/67/c4/217755fd1abc50d326c252922cd642002758095a81ff45010337b8b3ef65/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626", size = 267033 }, + { url = "https://files.pythonhosted.org/packages/b8/d7/34d8e404e358d2adcc5a228c2134643af00104c8fb0bf525f3688d756f05/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5", size = 252045 }, + { url = "https://files.pythonhosted.org/packages/5e/fa/40414471acf0aa0692ca77305aa00e434fcd8288f0941c93c30e9a5f8f2f/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774", size = 264866 }, + { url = "https://files.pythonhosted.org/packages/32/90/fcc850bae791abd2e0c041847f13e270aa08692a79f3e00de6d2dce1cb50/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7", size = 253932 }, + { url = "https://files.pythonhosted.org/packages/af/af/53afe99068b3c10b4cbae592a52ef72a7c92c0188440e83ee3a078fd8f75/charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9", size = 180320 }, + { url = "https://files.pythonhosted.org/packages/c9/bc/f46a132041b29e4a8779ed712d3df1bf112e94ca8de58b66d7ec2c0cf8b9/charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712", size = 204174 }, + { url = "https://files.pythonhosted.org/packages/a1/5d/9ed554480eda8e447b673648628fdc29574d23dbad01fe11837adedd1cae/charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7", size = 184126 }, + { url = "https://files.pythonhosted.org/packages/3b/32/9b8929bf384061ee1fe5d9c27c6f9776d3d824039ad4e14c88ec00c7808e/charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663", size = 381441 }, + { url = "https://files.pythonhosted.org/packages/96/10/e9aa7923d3ddac652c99a1c5f7be494e737e151566a44abe018daf757f2c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11", size = 241742 }, + { url = "https://files.pythonhosted.org/packages/28/53/a2d249ebddf47b889a100c0bdcb61a2f9dbb8bc24ef325cc062e4f476877/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc", size = 235298 }, + { url = "https://files.pythonhosted.org/packages/7d/07/469f78af590f7d5cd48e20d8dbfa3d66deeff9ba37768c04d886b5afd45c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a", size = 262500 }, + { url = "https://files.pythonhosted.org/packages/55/66/3bb56a47f7dcba014055b1a1d33c6f08bbe9c1e74dba154cfa25f90ae885/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4", size = 258888 }, + { url = "https://files.pythonhosted.org/packages/ff/c1/2adc2800903fb013210349313b710a5376856578d9e33e6b9a1d8b36714a/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004", size = 250243 }, + { url = "https://files.pythonhosted.org/packages/95/b5/a18d0dd1157ab655cc2cb14a545f4a4784bbad70ab3502412e36097502d9/charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b", size = 249871 }, + { url = "https://files.pythonhosted.org/packages/ad/c3/525f508cd1e58d0450ac55ed40ac75bc3a97482c59def5278456a5fbf03c/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263", size = 243580 }, + { url = "https://files.pythonhosted.org/packages/7c/c1/49a91fe7e97c8140094ca5c64161ab623a70d9f636bf834eace14048acb5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee", size = 239807 }, + { url = "https://files.pythonhosted.org/packages/d3/58/56a48c296601274c4689b864a8e2dfb209b81dfcb39472753ce95eea662b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c", size = 264083 }, + { url = "https://files.pythonhosted.org/packages/10/4c/dc48409274a1817ff349711d26c62aa0c597df865d4d69ef79160c859193/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e", size = 250317 }, + { url = "https://files.pythonhosted.org/packages/81/58/d325912115caec62d6bdd77bbab5e0b7da5d234a9f20affdffcbcb530d0b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d", size = 258173 }, + { url = "https://files.pythonhosted.org/packages/34/f7/b13b1ccae2c8ec63980d13be1890eb73f8aeabbfce02a24aabc0908788f5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61", size = 251960 }, + { url = "https://files.pythonhosted.org/packages/1e/25/ed3f9919c5aef8cc818be1f972f565f7610d7b2076b8ebb98839516ffc3c/charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f", size = 191186 }, + { url = "https://files.pythonhosted.org/packages/69/d5/43c2b3e9d8267092b913eb8b0603f0f71993c395632886bd37a7223f96cf/charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb", size = 215947 }, + { url = "https://files.pythonhosted.org/packages/a8/76/9aad3e9c8865e5e0efa9a7f6f81c37a67635a985145ecd44528a81e088ee/charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a", size = 193909 }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467 }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057 }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930 }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822 }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037 }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097 }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166 }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821 }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529 }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348 }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234 }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917 }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846 }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216 }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764 }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318 }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658 }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "colorlog" +version = "6.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system != 'Linux' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/55/ba79756cb90c8d69d599d57785398ac87bba7b19c80e87f4e8a562197c93/colorlog-6.12.0.tar.gz", hash = "sha256:2a7924c1dadf18b22a0eb8b06d1c7b01d5341707ec1641eb6fcc4fde0c3e8e5f", size = 18151 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/19/0b6647bf5e331521e55d2b63bfbdc210bd9cd605189273f03614a05f702d/colorlog-6.12.0-py3-none-any.whl", hash = "sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e", size = 12239 }, +] + +[[package]] +name = "coverage" +version = "7.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/66/edcec7d7a0b524aa8923e22925fde6fe50ce005a113dca13ae1581455c4c/coverage-7.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbac5abad70df71019988f83f26ac7092ff2642975def4429e98dc7585ef3490", size = 222367 }, + { url = "https://files.pythonhosted.org/packages/e6/c6/ab8de429e2e8548faf58ec7e1674a4ce00414b4113942d3fe87109cf0f68/coverage-7.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:357a173465c7ce028d07a95cc2b63b5bf59f50ecdd5ad75c5cbb78ada984048e", size = 222874 }, + { url = "https://files.pythonhosted.org/packages/be/c4/3b7b49587e8a6b9af79b3eb468d443d6042b6d65b47aa26586846a0d6566/coverage-7.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21b803935e2efc3acebe9697197a294fccf5dc4e5382bd6369542ff7a7d2a1d7", size = 253287 }, + { url = "https://files.pythonhosted.org/packages/fb/65/ec03b743a2a229c72cc1eff3e57be9d3564e9c6b4d5aba2d70744a3fc0d8/coverage-7.15.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a2b580774a4786c1053157c0165e04476e03ff293993d7c148eee784a94bae6", size = 255199 }, + { url = "https://files.pythonhosted.org/packages/41/4b/5163729e4b6582d61975cfd3ccab45b4ec53e21cf156d9941cb025188468/coverage-7.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9464451c4efffe8d47ace5a540b10b0dc10e879066290f8600872b7f54a419d", size = 257308 }, + { url = "https://files.pythonhosted.org/packages/86/08/2167a0f08fb87d702fa423a48578a32865464b7c9e1db3911ad7812ab414/coverage-7.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de602f34123c2f4af1c1869c6dbbbd60da6d5983bf01937367295d135cccbfce", size = 259268 }, + { url = "https://files.pythonhosted.org/packages/1e/e5/68eebae3053dbd48508edea559c21b23fbdf3460784f91370c83a86a6acd/coverage-7.15.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6879ded16a27f3eeca19b900c147e81616e7054db451471a611b2755ee5249f7", size = 253392 }, + { url = "https://files.pythonhosted.org/packages/1a/46/fd4ced40a2b691c774e515c9b69500bfa64c7960b67fcee4b2f6fad97fc3/coverage-7.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:986be58c3ab54aae8d3496a6225eea74f760fdbe739b38bd442c7e8d133aa53b", size = 255001 }, + { url = "https://files.pythonhosted.org/packages/53/25/ae2e5fa710bb6957a9aadeb9e3598d3b3e4af6587ce857ad42e8639a3f30/coverage-7.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6103639613fe6c1e989082948419bc77a2d26b6c825c99d7fad25f7d3d87afc", size = 253061 }, + { url = "https://files.pythonhosted.org/packages/d7/31/67ddc0365db2c6e93ac8580bc4bbc50f65273262f973f63ebcdbc15c0495/coverage-7.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3af93dddb5659276c63bc16ac6466ac2033a70ca816097bbc06345b8ccdf571", size = 256831 }, + { url = "https://files.pythonhosted.org/packages/f6/78/82b8fd18f57fb13f12d98fe874995bb2c4f9f17be8aff762c426323fdb96/coverage-7.15.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b10075e5421d04265766a6d1dac809bbeb8a946fbb23c8f82c227409b2190719", size = 252781 }, + { url = "https://files.pythonhosted.org/packages/0a/eb/6c74ef4dd12b252e573c49bdef9e2ac265bf3dbb79b8d7feb3266e084e9e/coverage-7.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a67a9f78b2942d87ba8ce3059c642164d2aedd65337377fb52fe9803656bc5c7", size = 253692 }, + { url = "https://files.pythonhosted.org/packages/5a/66/eb9aed1c3fd2d36ee00eb173f434b14fa607fc056739c9a89ff4244010ea/coverage-7.15.4-cp311-cp311-win32.whl", hash = "sha256:69484d1aca26e322e1c3ce03f09341e84524ababad2d7202161738d83cc9f82e", size = 224461 }, + { url = "https://files.pythonhosted.org/packages/e2/6d/81fa4161dfb3ed9d74e40d58647eff83a56b7612e78352581280fce2f477/coverage-7.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:63fd6fcd1dd6e158f7eb78606e72933b3f6d01e7b747f99c6c12d764307a0fdc", size = 224937 }, + { url = "https://files.pythonhosted.org/packages/5b/c1/d8dacf683c6cad3cf85ce68fd3774a6774ec402128822fdfaed920f11e6a/coverage-7.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:ea82116c9893fa89e929b7f197ee5a1950a76e91cc5c85ba503fc02379d04890", size = 224479 }, + { url = "https://files.pythonhosted.org/packages/1d/48/bc8d4ba7b37551a767bd863f15b3f80182b271c2f55975356f5f7dbe94c2/coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22", size = 222543 }, + { url = "https://files.pythonhosted.org/packages/20/dd/88d6f83f1fffc974a3691a34a97951c5b12df7512a6782c5963883cbc058/coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97", size = 222905 }, + { url = "https://files.pythonhosted.org/packages/bd/5c/54ee0d4748585bb0acab9891cd8d92f2d3593165b4e59fc9de113bfb3140/coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d", size = 254407 }, + { url = "https://files.pythonhosted.org/packages/8c/3f/f0642a372f494bd0d7dad3b497083b910194a5f1c88be2c94fef707c3b59/coverage-7.15.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:899b9da30f3c6c336566e3707495bb23e8302d39d862f01fa78c48b99b9437e2", size = 257145 }, + { url = "https://files.pythonhosted.org/packages/71/17/8b46d0ed68251016002ec972c8fc0119961a765d0984cafb8bf317c43758/coverage-7.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d15715e8c46552827e5e4f30a35575a2dbcad14454cf3284c54483946bd16931", size = 258257 }, + { url = "https://files.pythonhosted.org/packages/30/b8/8498a0e72d0adbe15477dd07463d2b3bb2c9f6a4815e8589e50939e2c3ae/coverage-7.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:002a438859f7b430bc99afeaf01a6d187dad1d0dc907b64cdeffc632a5db8fd8", size = 260517 }, + { url = "https://files.pythonhosted.org/packages/41/e1/7dce19c3bdb1e3dd63e769508216500edad81bd5f69a26d724e32aceaf78/coverage-7.15.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4193a04b518f7968f3099755f5509ee7cccc6dc2b92a6b14841934d22e222c9", size = 254785 }, + { url = "https://files.pythonhosted.org/packages/dd/b1/e1494703c675a2561723cd9b89f45c9168782c31280c611b1f767851e57c/coverage-7.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e98dcc55d572b38e69d117da7e8e8efb8500f1f5eaf81ecd460a63220790b839", size = 256176 }, + { url = "https://files.pythonhosted.org/packages/73/76/a5629d270fb638a43a4b10466f51e2f49d532c1aa4da2913cbbb150bbe0a/coverage-7.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:af6c538498ce66c10d3fd541c2a8d5b03da5850355add34e6cba564210cb9e72", size = 254321 }, + { url = "https://files.pythonhosted.org/packages/ff/4f/9c44447218435d5766b911534f9d798144a5560f85e9a54ebe5f3f5d19f9/coverage-7.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1d10025d96ea89fc2f73714dbc4cbd433fe012c1ac9e23f895d7728b238b6e52", size = 258390 }, + { url = "https://files.pythonhosted.org/packages/de/36/c1e127616fb3fa18a9ff71e76c417f2fd7424332a4870015ac224ef4c039/coverage-7.15.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d802e1947603162ded419bff83ac7489820355d2b856dfb09206574e3a37ac0c", size = 253894 }, + { url = "https://files.pythonhosted.org/packages/e9/b9/fdb92c8ae7a8bb9b850cc253b7b3b9c8526f68130002048b5671cd510d09/coverage-7.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2de40895718f91951b86712b4c5b694acaf9a0a49be13874896f599a1eed3f4", size = 255763 }, + { url = "https://files.pythonhosted.org/packages/6f/c0/a7d51b2587c7bdb76e71b0896d2565bf7d60436b5122fc83e511adb1f7cd/coverage-7.15.4-cp312-cp312-win32.whl", hash = "sha256:5c3431b2161279b7db5c2a1aa58ae02e5cb8c3c42d93a5094be3f5537bd5b11b", size = 224597 }, + { url = "https://files.pythonhosted.org/packages/49/b9/5c5f80cc55f5acaaca6dee677626bfcec8c87204a7809b438b08e84f4571/coverage-7.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6befeab5fb2b51c958ca4ac6c5d141a1e8240f4f76e46350f1911963deda49cd", size = 225135 }, + { url = "https://files.pythonhosted.org/packages/47/e4/2a4561f89ff6bf7c925c287d0f2cce8bdf139c3a33735c87e3203401cf94/coverage-7.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:67bc345491ab55b837277d76f5775d057e8c7f1ac44d890d8c2c82adde258c6f", size = 224515 }, + { url = "https://files.pythonhosted.org/packages/f1/84/651a9310859673aaa3b3203f1aa1641ca60fcf2494683e1c9474c7172780/coverage-7.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c705b28feb2775dc82a25f1d473a370bc37ff93f5177f4e29ce2425f560f6921", size = 222565 }, + { url = "https://files.pythonhosted.org/packages/82/f9/4dcf700137e8af550670f4d74d1b63828ce93e1e2b05e5f10710eb2ea987/coverage-7.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ff205ab5e3ecc670f6a4dd19d9cbf12ede53dd41cfc1e15716ec961ea6d314e", size = 222936 }, + { url = "https://files.pythonhosted.org/packages/07/4a/612ff1e780b3fbfd637486f542f84adc5503873d8b5d279dec1ffeef9414/coverage-7.15.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5172326e861a38b48b48befca15e0f477a26b283337a33a739c8fed229934e36", size = 253926 }, + { url = "https://files.pythonhosted.org/packages/b0/04/d1cff1c2ead4708a6a79c01d3736b6a25bd38a36678398f72a8dd33dfad9/coverage-7.15.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12b59c90084e3234fb11184886bf4a40f4f16a8c8f867be2e087b81f8e8868d4", size = 256523 }, + { url = "https://files.pythonhosted.org/packages/b9/80/d34e13fb4b293cbdb9665838cf5522077b8ad14ef947550631a4bced36a5/coverage-7.15.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349062d66f00b40fa2c1c222438bad25fabf755631b5d82937fe985c8008615c", size = 257759 }, + { url = "https://files.pythonhosted.org/packages/0f/e7/2c5fe7636fdb0732fe0f09f308a5b066864078b7fc61f6678e8478554f2e/coverage-7.15.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4256ced708e598e05209bc1a8ab4074e04a51dba4c62fb45926a229af675ace7", size = 259890 }, + { url = "https://files.pythonhosted.org/packages/92/28/9689f0858dfff59c2ea688938ab9fa2925631235df67126a42b6c5c70ae1/coverage-7.15.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d80f974b20782d9612c8b4c9beeca867074c7cf4079d1419843fa25a26428b25", size = 254121 }, + { url = "https://files.pythonhosted.org/packages/f9/e2/785077c230c157243eb5aa9a26c3be260ecd02001bead54a3cada3df8e03/coverage-7.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e179f19bfe1d31f8eeeaa12990194d761c4f62f0759661000bca6cd8729f40b", size = 255891 }, + { url = "https://files.pythonhosted.org/packages/d4/90/e20371b17b40f912f21305c2db2f30efa3de306f7320fc916804872c85a4/coverage-7.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8bc16bb47b7679670eceff71d78bfb7d6e5b143f6c2cd117487ec7c75e0d4b78", size = 253859 }, + { url = "https://files.pythonhosted.org/packages/05/49/25371987ee459a5f67c0427fb75c74f9358e65f2c71fe75bf41c1b6c5fcb/coverage-7.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd685005cd2c4200adfc14cf39a603b9320efab3f18a8f7f156d20c9cc3345f", size = 258011 }, + { url = "https://files.pythonhosted.org/packages/30/6e/32e67467f6154bf4f1c4f63b05acc5097cba4237d45bbeeea446b52e8ac1/coverage-7.15.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:337399ad2c93b3acd2a937627dae8b3e86b66707cd3d3e856347999aadf1ef8d", size = 253676 }, + { url = "https://files.pythonhosted.org/packages/03/c1/8b24192e89286399765155251f99ee9f070a9d637109018ac23d99b99f6f/coverage-7.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:96e257121228ec5cd2bb919276e94ac11074471bc37d68dbae0e8308cce15fff", size = 255453 }, + { url = "https://files.pythonhosted.org/packages/16/6f/8b41ebdf67c87854e17c035336a90f1cfbad0c14c2a584301be6ff148718/coverage-7.15.4-cp313-cp313-win32.whl", hash = "sha256:c65a9e0dfc6143491879da4e13b5e30f8be192055de508d737fb14601edbd22c", size = 224605 }, + { url = "https://files.pythonhosted.org/packages/e0/e2/2946c7f0b42b152ecb21ff1bdad72e3d301e790c0c487e4a86e8c9f69347/coverage-7.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:2ff8f5e9b8f7a94f0c11c45631eee103dbcb7d63274edd12c56efe1be690b3b4", size = 225148 }, + { url = "https://files.pythonhosted.org/packages/9e/83/3f4a69957f48ae7a0aba76c34743f88963d607b19e03f3f8e66f91cae0f9/coverage-7.15.4-cp313-cp313-win_arm64.whl", hash = "sha256:6e0a8a5083b096487d6cfced94cdd514d8f5db6f113610fb36c0620edb1028cf", size = 224536 }, + { url = "https://files.pythonhosted.org/packages/ea/ac/748cf29eeb2d6be34a3176ce26a4f49e38085ee08e8935f05f6f26ed7e0f/coverage-7.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:770e9325ab5ea6d56f77e59b29ecfe0ac20b57a82a601876f90494a4dda0386f", size = 222608 }, + { url = "https://files.pythonhosted.org/packages/0b/02/1abbf5c984677b0aa439cdacaccbf38d248939d8ef8fe1cc7a50d73edb77/coverage-7.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d12b33a3a50a1676b7784dc8d00a0c6d66a9f2add4b85a041c19b6a7e53ef23c", size = 222940 }, + { url = "https://files.pythonhosted.org/packages/eb/e1/ff8f9f53d9fcf586125b55d0b1f04ec1c14955fee41e83d5814bee141bb5/coverage-7.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5669c8378ebde86f5def7a25d29586631b58acc27ffde04399f678f3dfc6e082", size = 253985 }, + { url = "https://files.pythonhosted.org/packages/a1/26/595759762e514e81be1d7d01ed03444303bcd152226a6529998d253f9201/coverage-7.15.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff97a14362eef486483ed44042ca2027ea257df6ff768e62358ee0c9776925ac", size = 256492 }, + { url = "https://files.pythonhosted.org/packages/24/68/b79aabac54d482be23b5fcdd4f4662bff24a78edc4ee29201726929936d5/coverage-7.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a325e815318638aed1655d9c06e6d7c2d3d46c09231ce988070428a8762d734", size = 257837 }, + { url = "https://files.pythonhosted.org/packages/09/0f/bf7f297885a5bf6fd71e5782404e0ff059ca09e8711ceb3a08544abde45a/coverage-7.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:474223409d88eb20d2d6a0d37ea60e8647a65a90cc008dc1f0410af5f64f1e0d", size = 260152 }, + { url = "https://files.pythonhosted.org/packages/fd/f1/296744e854ff8368542343457414380465e9ceefb9192342feb9d3bc461d/coverage-7.15.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f2f62ae3cd189dd2e13aece758c57b3eecbd27be070dbd4cbd10936049e5dbf", size = 253978 }, + { url = "https://files.pythonhosted.org/packages/55/b0/bbdb2e9057493e66220a2e149ca2d301ba0e3a58a83bd6b90de9826d16f3/coverage-7.15.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39ece820e29e0a2ba34b3ecb3be83c27e997eed8926f2ba6fe7ce7a0bda5843b", size = 255846 }, + { url = "https://files.pythonhosted.org/packages/96/e4/38015b2b6d21258713bd17e76b59d033b191efb5703589cffd037dfbca20/coverage-7.15.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f21b56dcace11dfe013014201f577dcd592b2a9b72182d930361b47cf6f73f25", size = 253808 }, + { url = "https://files.pythonhosted.org/packages/0b/64/0d515c1e60ee6fbfd1a0e79c07cd87d388a233b7adc37758735677203808/coverage-7.15.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:93a3a0b662abcc10c73a47cbc72cd60f63618d6989fb2d1286e50eacd974f303", size = 258081 }, + { url = "https://files.pythonhosted.org/packages/91/71/04d9e7a3642146c6351338aef4ef85ab11dbbb54744c13245caba1aad1c0/coverage-7.15.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:141fae2cabf5569b782c10afc4c850ce10f618c13f8db54765cba99cc839da1f", size = 253624 }, + { url = "https://files.pythonhosted.org/packages/b4/a7/6c28b74c81ebff66987b0e2522ba5cffa3e90b0c33cb6a2eb264d4ee8cf1/coverage-7.15.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:81294c7e6ab30c5f74c0353b11b2fd6320e72d9bee6ac73b357caa8b916323a5", size = 255280 }, + { url = "https://files.pythonhosted.org/packages/52/af/bc19996a7014b98d7bbb0f0939453c67074af65784a3aa16a789a07381fa/coverage-7.15.4-cp314-cp314-win32.whl", hash = "sha256:7bbd7d6418e0dab31a206af5203bd43ae36edb8e7fba1940b055d3e9249290d7", size = 224768 }, + { url = "https://files.pythonhosted.org/packages/ee/90/219484e476d6e101ba0a444852579e05f5b75c37c611a42ed1190f73ef62/coverage-7.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:f0204ed122758782970526057093f448051a39db9d810d4e344bb87a3546f425", size = 225259 }, + { url = "https://files.pythonhosted.org/packages/b7/66/fa77daf4e383e5f776dac62c2409b6af81910ae6fe326bd5170dba74cc63/coverage-7.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:9e71e7bc71c686a123347ae47a0de33a175e797a85bb57b791492adf4eec8ed8", size = 224684 }, + { url = "https://files.pythonhosted.org/packages/58/5b/f03bf0ce362bbf3f785fa5219620d00778d4ac6fc9e407734828e9c672f6/coverage-7.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7c922735321eef3f87c280a3d39afff6b646723a2880b862cda4ac7a093b8aa8", size = 223338 }, + { url = "https://files.pythonhosted.org/packages/0f/76/e77d0ae22501831cc9f92193e8a957a5caa1dd177f90a6d1d9b106242d92/coverage-7.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f41c17c4668a655ce96d090d8d5ffdc24ef64b5a02f9753884d08483e8a4a41a", size = 223609 }, + { url = "https://files.pythonhosted.org/packages/82/1a/b1f089da8d38ac612fa2dd6dc7f4a1a7657d12f3e261d2996edd3a838d0b/coverage-7.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46822e9b6ff1c6a72b518c162c44a8f45a61a1d609c51084bf5b16c023c5037b", size = 264970 }, + { url = "https://files.pythonhosted.org/packages/bf/31/e66d98d6e9c7fcc88470f1e234eaf6b1950dc0dfbf797f7282c1c861da24/coverage-7.15.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d6f4955b73b5445271379a59e3792b0d978f42d4a01e0cf7a67d9c33a3bb0a5", size = 267088 }, + { url = "https://files.pythonhosted.org/packages/59/a1/ae94eb2c541add426378408379f233591e069040b1e2cdb33df9498a0682/coverage-7.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3fc9e047706fb4a9abb54f719d3aa643e80e5bb3818182c40aee01ac0f0247ba", size = 269508 }, + { url = "https://files.pythonhosted.org/packages/9c/c7/88a10694a1c6a213569766aba9f25847b28155d4ac731b13226db216356d/coverage-7.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05e491d4f3165d62d4f5c8fd48dfeabf2ae8f42cbbd484319af33ea851b78982", size = 270629 }, + { url = "https://files.pythonhosted.org/packages/b3/34/d8b8232e5e55169933b59aabcef2fedfa4b9d8897361bb80fcbda146505f/coverage-7.15.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:226c66e80ec0598d3b9b4874123df167ccca342aca8714f77cac6829688ee09c", size = 264043 }, + { url = "https://files.pythonhosted.org/packages/7e/35/58b009dbf8c471c7224716478b9fed4a7e1af15320e1ed41660978504663/coverage-7.15.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac41cc14bebda0dbfb0628036b7f75706935c95bcc07fefe9a0f93614aa60a57", size = 266963 }, + { url = "https://files.pythonhosted.org/packages/62/aa/57fbda1b42c892968273c56b6ee9dc0f1310850859230a507bc7873b1f65/coverage-7.15.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8af623e5cd92080acddd02b38f2f406a2c3a0893c38950b211890361448fbf26", size = 264569 }, + { url = "https://files.pythonhosted.org/packages/98/8a/360e6e7f24d477b7e889703af0afa878d15b6d4d8d2a822b2835c169a879/coverage-7.15.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07545711d4f0f32852a18f18ad11f76f0109909d09e78b9008b4cfc67e829429", size = 268299 }, + { url = "https://files.pythonhosted.org/packages/4e/89/6f701261aee21b6b5fa8f7872229406dc917e125069448292223bf213606/coverage-7.15.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a0865421cfdc53654b342d515e5a233187590882d20b95752150e53f65460017", size = 263413 }, + { url = "https://files.pythonhosted.org/packages/3f/0f/6f04036edc260ed425af83e834f627fad48941ce97b50bfe6edd8b6fa623/coverage-7.15.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:460115e32ee40566476db5048f9bec1e842c127ad8e6f8be745aad3ac9cbc839", size = 265725 }, + { url = "https://files.pythonhosted.org/packages/c4/ce/d19b5d4d5c49a7bfb925fd74310fee7d28bc99520ac3367ccbc54e662518/coverage-7.15.4-cp314-cp314t-win32.whl", hash = "sha256:cbde877ef9dd7baf272b9bfef2b8a25edd45d9170fc326951dd20eb480335e85", size = 225079 }, + { url = "https://files.pythonhosted.org/packages/26/bb/7aa1b3b173faee0679037ca950bbbe1247273656697994d8d13f80f8d4b4/coverage-7.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3da9e92d1c551fd7563833e9ade686efb0c4b7363ab7681a94283958c950bf5e", size = 225911 }, + { url = "https://files.pythonhosted.org/packages/81/1c/4ea9e47426d80038d9222db3c4534cb6021a74b237d3ff97ffd33b6600dd/coverage-7.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3a54f5a0d85050c73a38f6793090ee83974531e67fe5e57a1da9bee11398aa5e", size = 225219 }, + { url = "https://files.pythonhosted.org/packages/2b/c4/dc5d2ac8f9142e7ec7de66e7bf0591db29d78955a040bd915870d9c0e657/coverage-7.15.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2c9872e4d9dc5d3cf616bf4b382f5a00359305a5be666a3dd0b5cdb4e49597f9", size = 222604 }, + { url = "https://files.pythonhosted.org/packages/70/39/33e63df81fe2ee100897451841c821467635923e58e37c6bd4b46dd8106c/coverage-7.15.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:e101dbb4b9b72f0cddd8cdc8c9c5b47f456766f5e0ac82dbfb75e5c55409b78a", size = 222944 }, + { url = "https://files.pythonhosted.org/packages/99/1f/ef3ffb5557febc75a0d97aa459d0266d7d741110265121cc6d8539343d44/coverage-7.15.4-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7d1abebdb047729e852b9c77a00497dfbeb11eb3a117e037d7dbc3ac8e5f5c54", size = 254050 }, + { url = "https://files.pythonhosted.org/packages/6f/f5/1f0f6f77698c3601ca0ae7431e34b24c62ca2f06fecb23b73ed1f651d2be/coverage-7.15.4-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d28a4a899354d0ea6214cc59b4fa19eefbce1b9ff1688ab579acf49e894bd3fb", size = 256967 }, + { url = "https://files.pythonhosted.org/packages/03/7a/2ed9bed79925f4367c83c77f66a89e5ca7229c288d2d19ad5f36d1ca0070/coverage-7.15.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb3c2aacea411cc7e1d27712490c11108e2de1d39019ae32915493a59a8b9ed", size = 258587 }, + { url = "https://files.pythonhosted.org/packages/45/8c/fa34044f71b7cc4ecb6da9c2408770959b0591fa9b5fb6fb6bca38f94298/coverage-7.15.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9447978a92f405d301123cfd39ff49895490efb769a758fe2734c7f631bf8ce", size = 260785 }, + { url = "https://files.pythonhosted.org/packages/4f/54/d5727ce36b4524a7394ab9f5f1df378e1f23affcdab01037dc8655185cc7/coverage-7.15.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:050467a7983b8e2fe7dd41a78bb30c3e7f8c0b8cafda14b1c46f8b5e3cf2dd3c", size = 254545 }, + { url = "https://files.pythonhosted.org/packages/dc/e6/6e3783e576719590194bdffb6dd6d85490801785b7c331e35a245d8cb8b5/coverage-7.15.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d003b7a5708ddad5c206c79607a6b92abb6fc13c57d99d8a4468cc03a2941ced", size = 256682 }, + { url = "https://files.pythonhosted.org/packages/dc/f2/bacdbde18b69ed2de424fcf64d9fb0a4913753d4f0eca8bae9daad69f4bd/coverage-7.15.4-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c38efe30fd74e5c19e9433f11fb1f5dc9c6522770971b7c6145bbaa413dc8800", size = 254560 }, + { url = "https://files.pythonhosted.org/packages/6c/a3/1fb927196e3477c1b48831169ab58ba08f451ba87ae311ff1de68b26a616/coverage-7.15.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:1f4f826d70f772ab8b0c052329580d7fe8b8abd191e4ce0c8f81aec6614665d3", size = 258792 }, + { url = "https://files.pythonhosted.org/packages/41/58/30d4c149c69053de0edfe325614c1d28d508f62b1783e0e4a234d2e49136/coverage-7.15.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4a4bf917c9953f57c957be31c1cd504e3bd2f34d4a352b9d391a3025336f6768", size = 253968 }, + { url = "https://files.pythonhosted.org/packages/89/e4/77f639371b918aad30dda4051f95404b43578f7f2e2f87ba73e02ed1ff37/coverage-7.15.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1c9bf40ebef178a45192c75c4964760bb261b0e6ad725da5fc4c93f674f19753", size = 255893 }, + { url = "https://files.pythonhosted.org/packages/5c/62/13be29b3ddab35f14c87967a4820a05106d2a3eccb4fa4ff550bf30b75e0/coverage-7.15.4-cp315-cp315-win32.whl", hash = "sha256:43619d04c3671792d2c4706ae8bf45e265dc87bbd4078189ef8b847ea1e74be2", size = 224768 }, + { url = "https://files.pythonhosted.org/packages/a1/70/af0c6be0f964af6954f6b74bc109b0dbca02824696d2520fb17fe1ab06e3/coverage-7.15.4-cp315-cp315-win_amd64.whl", hash = "sha256:be619439dbcd31a2eab10b32de9fff62c26ed4bab69dc32b8363fdaaa0882809", size = 225242 }, + { url = "https://files.pythonhosted.org/packages/4f/2d/f3bd3aab899fc9efc18b53133ee68f5f98574ef480649b23e12962226387/coverage-7.15.4-cp315-cp315-win_arm64.whl", hash = "sha256:def597967dafc2e8d97c9097ea453c464e0bb8ed38f193a43070f10dc623bb6d", size = 224674 }, + { url = "https://files.pythonhosted.org/packages/f5/ca/f69251cd63eabc6438321aea22148754cce758a26bde07dd490e3fe7cfc5/coverage-7.15.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c7dbc748ac8a1e3e59a2b28bea47675e6e778081dbbf081bde0d75def2fcbe1d", size = 223333 }, + { url = "https://files.pythonhosted.org/packages/a7/a7/037b53b2885b0d8447064432491a4d5a1014cd9f97a594d53acd0c04541a/coverage-7.15.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:2413074a5ecbb61a01a7888fc72db0ca324d13588c5b38bc0dd8564cdcdfea26", size = 223630 }, + { url = "https://files.pythonhosted.org/packages/80/4f/152b8a4779ae90da11bb24f7467df8a59f0be48a5c52acb856325ca48289/coverage-7.15.4-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4e6f6f632b7b2f714bf7a1346e8f97b650ee71f3c298aaad42a2ab60f0f07645", size = 264489 }, + { url = "https://files.pythonhosted.org/packages/10/2d/84b4b9e0e1dd6528a51920ff7031f35b789382e467a28ec6a5a578cb8812/coverage-7.15.4-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8df457da2249d3c75ca2e5e835d59c725abfe92d27fdff6cd99eed85b51d5e9a", size = 267567 }, + { url = "https://files.pythonhosted.org/packages/53/fc/ba01cc25299f9f8a2c8b02d3b28c53f3543d9fbfbe4e74fa2760b48f163e/coverage-7.15.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:050f66a08805acb5b8a23c6d4a517b1ecf82c08e81ed0e4bd727df065e5c6624", size = 270123 }, + { url = "https://files.pythonhosted.org/packages/cf/d0/db2647cbf40b14f8c308f94ff7bf89c06d564e59f396906edf50086ec788/coverage-7.15.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1587fb771d1ccceef708fdde1e5af8c7ed24b486b61d13a321acb7d8145390aa", size = 271107 }, + { url = "https://files.pythonhosted.org/packages/70/ff/4d2d17924552c458bb4f77dd631f0e3bc92fbbdf2d2d916cd4b33bbfd5b1/coverage-7.15.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b4f1c3a69ca580f3fbd6b2046915f536d7f586874f25c1bb23add2a3c88d50f", size = 264955 }, + { url = "https://files.pythonhosted.org/packages/ee/de/dc010c7a3691f396d93bbc26bfcafa1c2a3a351cd520470f15faf5795bd5/coverage-7.15.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ffb58d7eff5b7f6ecc6fa21d6288ab7f968a212cb67d682c269c09b9eba3b66f", size = 267949 }, + { url = "https://files.pythonhosted.org/packages/78/ea/dc96a11375e83c045c2f7c61fb6918277cfe9401db7c0f7b1d111a84b2e5/coverage-7.15.4-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:d9df165544774574ee004b953023d1bebada1894a80b1052a43d798b0f676e67", size = 264421 }, + { url = "https://files.pythonhosted.org/packages/c8/86/b77131a0f9503ce461cd577076147d7a9040f0c5dda772686f729e2cc9cb/coverage-7.15.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:f9de0a24a4079b53e523b5c5e2c5945ec251ab486652659955187cf255a259bc", size = 269121 }, + { url = "https://files.pythonhosted.org/packages/24/24/944bc35007862955e7ebf05754e645419dcf5d7526c52735cfa2715e8ebf/coverage-7.15.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:150089274bdc9f940628552cb92844e0223c987f1902ab8efe9f45a2ec758d88", size = 264565 }, + { url = "https://files.pythonhosted.org/packages/c7/cc/a3bb9f93e7e740659163e2ea584f8196ddcd2c456a5dbe15f6c50105fec1/coverage-7.15.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:a58a94fed5da6997d258e8f7668c1e195fbd04a691d781b7558f1e468f9e68bc", size = 266522 }, + { url = "https://files.pythonhosted.org/packages/49/dd/e0e40f3560d878d888c580698ff5ad1179f5e1c3ac949684ef66b41a3817/coverage-7.15.4-cp315-cp315t-win32.whl", hash = "sha256:ebd5a6d8466ff30836572f3ba2cae8a5e8f85029b1c6d5e2ed338dc472a5166a", size = 225068 }, + { url = "https://files.pythonhosted.org/packages/c6/7e/37732ea80eebc30e976e4cdab15c190bc42d96959a42e38ddf6f8c60468f/coverage-7.15.4-cp315-cp315t-win_amd64.whl", hash = "sha256:288bde2a2d7ab6b6c2d7252fcde8b524387f2d970bdba9658fc6f8bbcaef0f9b", size = 225895 }, + { url = "https://files.pythonhosted.org/packages/c6/08/1e00f7923eaaba45fb3d51dd794125fc766304b1df264f3a9c6557bfb30e/coverage-7.15.4-cp315-cp315t-win_arm64.whl", hash = "sha256:68be5e1de60ff13c9095bbec0e5a7fa45b33b101752215b91345ea1f61c4a278", size = 225213 }, + { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cuda-bindings" +version = "13.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder", marker = "python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539 }, + { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166 }, + { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351 }, + { url = "https://files.pythonhosted.org/packages/39/2a/6d2e9047d1fb243dbaa364b01e0297534b9ed7fd27dba1c9f361519cf69b/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32d08f71ebcdf00f0f41eab2eb37e8da94c8ed411cc9f7f7a019ce6b34abe3a", size = 6657965 }, + { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504 }, + { url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660 }, + { url = "https://files.pythonhosted.org/packages/b1/81/bff68ce829999c1e4209c761bbf903b1c06ec570416ddb25020864ad5907/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ab2f74ed65bfef4163ba07a8db16f1085e0729291db12a2423aff84ee8278b8", size = 6013639 }, + { url = "https://files.pythonhosted.org/packages/d4/e0/c8a1f0c8f9ffdea4f5fe6dbab89b326cef4d85caf489dad39e209da89416/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd4c814d311ec08c981f6dded1dbe7d4b371067ee4f6c14cccec4bde9590f80", size = 6534419 }, + { url = "https://files.pythonhosted.org/packages/52/b8/83b1f563925b290f2d11a01a77a84013ba56052fe3653a5bef3ccfbb43d6/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3c772dfff49681541d59630c90f858e173ac926b9c593a2b7123f2a1043cc76", size = 5809771 }, + { url = "https://files.pythonhosted.org/packages/12/20/e79b4bfe98f075195afb6343d41c498f9dbd2d161d7021d4d28bceb83581/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36febb7c1079d68a981dbbd8d5a67235b399802b82075c9388624719607e52b9", size = 6358584 }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/b1/ef21259ec74fe0b265ed201379de1d0ef7c14178313ee03705952f1b7093/cuda_pathfinder-1.8.0-py3-none-any.whl", hash = "sha256:c44e574dc997fae2814721d1ae97d0fd6db76db82decbe9b753bf75de53f515e", size = 62539 }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.3.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512 }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, + { name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux')" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] +curand = [ + { name = "nvidia-curand", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] +cusolver = [ + { name = "nvidia-cublas", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, + { name = "nvidia-cusolver", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, + { name = "nvidia-cusparse", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "(python_full_version < '3.15' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform == 'linux') or (python_full_version < '3.15' and platform_machine == 'AMD64' and platform_system == 'Linux' and sys_platform == 'win32')" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019 }, +] + +[[package]] +name = "doclang" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typer", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/3a/005e4856ad8e9b9879414a4df4dbc56dc3663b96f9d8c920ef210e8931cf/doclang-0.7.3.tar.gz", hash = "sha256:ca50615357e46ebf9597bb9065b9112367103ec24bd539f8ae12649224cf50b0", size = 31569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/81/334ccc0f0cd7c3d75996b6b596e7f4c62c4c46a0ca042003315c28170159/doclang-0.7.3-py3-none-any.whl", hash = "sha256:9440c4ca9f7e061a7b8d33bdf15b1029be69a4c13cd8952dd6ce541884e4c685", size = 32267 }, +] + +[[package]] +name = "docling" +version = "2.123.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docling-slim", extra = ["standard"], marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/d0/a9db20eff07574cb9aa6bee5a9d0cbb4ee17f70d6d0405d11fac9297ff6b/docling-2.123.0.tar.gz", hash = "sha256:d4ce15371d4fcaad57a2a31672fe69d9ebb8100c9473b6de06ff5d84499674bf", size = 9070 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/4b/cdea48e4e914d246ece99f1bf97b3f983f4db91ec355024d50657cf587f1/docling-2.123.0-py3-none-any.whl", hash = "sha256:95c0a4d9bc1beafc6097c8573ec3a8dc317e8bcf67e3234aa7c050b7d73fde9c", size = 5223 }, +] + +[[package]] +name = "docling-core" +version = "2.92.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "doclang", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "jsonref", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "jsonschema", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "latex2mathml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pandas", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pillow", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic-settings", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pyyaml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tabulate", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typer", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/9c/8ab960c09e501b57dda1b823dff3264bed70ab70ef60328767be38ca30f5/docling_core-2.92.0.tar.gz", hash = "sha256:33fd25e38c199336447a21925374400aca13a6b9316a032f111972c2dc0f085c", size = 360897 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/d1/7f6d9e737ea5c41c0fd4d2819732fc43416c0a60c5870342381db149d368/docling_core-2.92.0-py3-none-any.whl", hash = "sha256:726d89c23197e53be2f7192bb4c00108ff545830cf9ab7b981fb014fa92b5c02", size = 295507 }, +] + +[package.optional-dependencies] +chunking = [ + { name = "semchunk", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "transformers", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tree-sitter", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tree-sitter-c", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tree-sitter-javascript", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tree-sitter-python", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tree-sitter-typescript", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] + +[[package]] +name = "docling-ibm-models" +version = "3.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accelerate", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "docling-core", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "huggingface-hub", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "jsonlines", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pillow", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "rtree", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "safetensors", extra = ["torch"], marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "torch", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "torchvision", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tqdm", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "transformers", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/17/6ff282d494a6d2f4299ca2edb2d4f353ff46f4512868284565ad77e0221e/docling_ibm_models-3.15.0.tar.gz", hash = "sha256:bea2c97a65657e6343877126c8c9a815599724edae6eda63df55e612276c2d3a", size = 105732 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/ca/a74621564f89f0e37223e5037f280cb0cc2b6c473f22451febb12d5ef63f/docling_ibm_models-3.15.0-py3-none-any.whl", hash = "sha256:5220773b8731c269b4edf3e0b02dd1703aff22721d84c40773af8f39da421db5", size = 95702 }, +] + +[[package]] +name = "docling-parse" +version = "7.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docling-core", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pillow", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pywin32", marker = "platform_system != 'Linux' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/ee/83a4fb9926b0fb0185b30797a98027a8e433284a0bf16396c11fb85a09b6/docling_parse-7.16.0.tar.gz", hash = "sha256:870e781f7b7d1403d1168bf860c19fd2ab5ddb42335c60a1927673fa8fc615e2", size = 6963225 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/d9/96c1b4a0ddfef5ce81b126a1d34aefcc1fa36cf19415626c48183f1de790/docling_parse-7.16.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:d81008264fec675bd75ddbb79ec20823dd638f3ab31d889e08991d769cc592ab", size = 9761145 }, + { url = "https://files.pythonhosted.org/packages/ea/a9/16b21173aa41be0f922b410a96960b17940f3f1a882263b0192bc17b031e/docling_parse-7.16.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e63af02e5bcea71a407776e4f8d944f5db4ee840cb3abb9c4ecaae6768e0efb", size = 10283267 }, + { url = "https://files.pythonhosted.org/packages/f2/bc/5665ac3ebd14af94ed08ef0ba89d1b87541c0fac64ed27dd4c853f1fd40e/docling_parse-7.16.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96756c2205aaa958ee2930e7ded33b756c5ff663724f8aa7dce5cd241296cf1b", size = 10683338 }, + { url = "https://files.pythonhosted.org/packages/7b/e1/9f31ca9bd22be994c6651b1e080882bb8a787a630d25e666b3523d78563c/docling_parse-7.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:e6104ae8e881ddb6ed78275c662cf857dbfbb2af4853189cc1d4ba57f2a1ae1f", size = 11741343 }, + { url = "https://files.pythonhosted.org/packages/3a/1b/1986384533c025c9e40823d303f5b1958d1730b595b02de57a7a904bef4d/docling_parse-7.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:7b400182bc6de50cb7705eb7d944fe5dfe747023a5b8557adcd643a5c014880d", size = 9051339 }, + { url = "https://files.pythonhosted.org/packages/08/e3/02e6e02eef860e276587c90896dd86d897e1150bf984c6135f4ab61f9352/docling_parse-7.16.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cb173b447806ff3a4af3c06936e9585dc201b3ecbb958aebde37a2725471675f", size = 9762630 }, + { url = "https://files.pythonhosted.org/packages/25/70/3702dd16141ac8e45548d3f09d2e66c373a96ffc871086a0bb022caa81de/docling_parse-7.16.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c2bf844d567f729aa2a48f22b3c4f5d7e5a7db6b6b3a9ec07b45cf31d4ce3e", size = 10286523 }, + { url = "https://files.pythonhosted.org/packages/c1/cb/cece74107282ce7cfe00d14ce5a33d2056aed689a34d20965be4779972da/docling_parse-7.16.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab71b6b9d1085b08ca2a3529356ae3122e56a8be693a32d37e996ae88c6fe866", size = 10686779 }, + { url = "https://files.pythonhosted.org/packages/51/b3/52068732b63eae2cbae779d057c311e0010e761e5853ad233080cf201a53/docling_parse-7.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:f1abfe050b670bcb25c7bcefc5d876bc3561949c1bff177e8a8a3d8d6f217314", size = 11746880 }, + { url = "https://files.pythonhosted.org/packages/23/09/2ceda924e1581c4b9fbec47d1343e0e64078ef89a314383dca2f604e6b48/docling_parse-7.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:da0483decd77d9a967c54e751107c36a3c3bae186e989f81339dff5037f0a7fe", size = 9051750 }, + { url = "https://files.pythonhosted.org/packages/68/b3/1a4b65a60e0cd11d4512c55e81c03a4f5f6c7c08ed6f78fb703e983aa4b0/docling_parse-7.16.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c3b4d67da75631a28252bf574023e4a45fdd422603ea07cf73a8e70074547436", size = 9762652 }, + { url = "https://files.pythonhosted.org/packages/bc/fa/0b1a0ce72403002080231708f753cbbfbef22385b336bfd5f2252a96fe54/docling_parse-7.16.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e2bebe14d3ce0b6e7f256f2e4bbc7f2405dc9ddc91bbc5031dcba07bf97d9b8", size = 10286719 }, + { url = "https://files.pythonhosted.org/packages/c1/67/fe434aeb6d58bf3a69bdbcfee7b52633ec2cfad5e28ce7862d3d910f31c7/docling_parse-7.16.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ab9b9deefd347166d670f894d27f697bf2d213a51e526516dc9ff44e0c19b5a", size = 10686793 }, + { url = "https://files.pythonhosted.org/packages/8d/79/97d07a4a056f540d7224720eb1cbdd606fe5f825d7954b5b574cf872efb8/docling_parse-7.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:b4dc06844b775014bbf9a8f2d830d6700a47b2ec13c62a4ee61a2c9382fe4dca", size = 11746783 }, + { url = "https://files.pythonhosted.org/packages/42/eb/61492ee31917fdcc35117c0b5053626b81084b68cc1345e779e2428c6e08/docling_parse-7.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:4dad04e693ec3d120885249a097dc61b0823f01b0a99c52696828b9eb6649e0b", size = 9051766 }, + { url = "https://files.pythonhosted.org/packages/a6/ac/9ca727496edf2a1e5f2ecf7b2736173616d4e09ae65e4f7f858f9dc5d8b3/docling_parse-7.16.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed3fd24a26a2b00357cc199001e2817d70163447b456ac0c66efe1823e38404", size = 9763549 }, + { url = "https://files.pythonhosted.org/packages/13/86/84ab1b9bdc4ab0eb68bfef70b5abf871fde4f9c8f576b1e5e40ed0187c22/docling_parse-7.16.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12a1b9d145cc44d94a7b80e4ef62ea732b00cbd7803b1dac0d803893a82c100c", size = 10287706 }, + { url = "https://files.pythonhosted.org/packages/d4/ec/887fb066d848ed0c8ad9efb90d1fe0361ee869b0a95a55a0254e70f723f1/docling_parse-7.16.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e71584a38d8bfaaf7947557b51359a260266b348507b0682814fa39f33cb2f0b", size = 10687139 }, + { url = "https://files.pythonhosted.org/packages/ba/39/99699251e782eb8ffdbe2c49b7454d9bab8f0bed7cea30c56a8a1c564f0f/docling_parse-7.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:9c11e3c7cb509fa27de277795211bd80369a2e682ed0e3d7170e12df13ff6d05", size = 12180438 }, + { url = "https://files.pythonhosted.org/packages/95/07/fba291257e7a40f9fddfca25fccb11f26404daabb83fddacd63cee72b307/docling_parse-7.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:c9d5994bdcb8b12d157871ca2fd1e399afeac4528a2343af7d82e87dfca35e82", size = 9419759 }, +] + +[[package]] +name = "docling-slim" +version = "2.123.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "docling-core", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "filetype", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pluggy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic-settings", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "requests", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tqdm", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/3c/b803618e98924c76e291a595d70c0db30c2fa175ac13e9d72d2522525634/docling_slim-2.123.0.tar.gz", hash = "sha256:3d78255443c1c734cabd6e84b4de7c8dcfeb7c1b0b0a2dc39185bfa22410638e", size = 612318 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/47/a5a1761580d46fda4d6023da961822d9416501f4f35bad0672f613819555/docling_slim-2.123.0-py3-none-any.whl", hash = "sha256:f46394d7595adf44a8068109d51b65fd6582227a7ff636125dc555deb4d2b4da", size = 767567 }, +] + +[package.optional-dependencies] +standard = [ + { name = "accelerate", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "beautifulsoup4", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "defusedxml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "docling-core", extra = ["chunking"], marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "docling-ibm-models", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "docling-parse", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "httpx", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "huggingface-hub", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "mail-parser", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "marko", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "openpyxl", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pillow", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "polyfactory", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pylatexenc", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pypdfium2", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "python-docx", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "python-dotenv", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "python-oxmsg", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "python-pptx", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "rapidocr", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "rich", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "rtree", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "scipy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "torch", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "torchvision", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typer", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "websockets", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, +] + +[[package]] +name = "faker" +version = "40.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/fb/35acc76128d5ee8983d940da871cc8eba876e7b0e9e6bd402ecd7104dcdc/faker-40.37.0.tar.gz", hash = "sha256:a92dff7f310e61fb544c61720e15edb2e7448bc33d15a321a99e9ab7b94abf54", size = 2025920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/22/bf589b6b2c7527047f55450358fbe5961aeaadf168d6b51a1a1c67da497f/faker-40.37.0-py3-none-any.whl", hash = "sha256:ddbafa55c94d5b69c08ced3a7f202614204a02e07ba6548c729b8d18acc0b490", size = 2062853 }, +] + +[[package]] +name = "fastapi" +version = "0.141.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "starlette", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-inspection", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954 }, +] + +[[package]] +name = "filelock" +version = "3.32.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/30/03b03951873a1a0ffc7e8ca0e10c15597b59e8d0e39260704cd2ea087bc4/filelock-3.32.4.tar.gz", hash = "sha256:2bde2e4cf732e0153406d8a7bc80620ecf5e621fe0d25e41143c4e3b4733ff30", size = 222126 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl", hash = "sha256:22e58ca3b1ae3b98993b762d7338367ae64fe50252bf78d59da3bfebcdf1cedd", size = 99864 }, +] + +[[package]] +name = "filetype" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 }, +] + +[[package]] +name = "fsspec" +version = "2026.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583 }, +] + +[[package]] +name = "greenlet" +version = "3.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/d8/7cc97c142388aef03f622e001c572c4f84e9252a439549d483f555771970/greenlet-3.5.5.tar.gz", hash = "sha256:adb4bae02e91a8e863e48b177e4014bdcac8a6b5e047ea1df687a61534b85e6c", size = 207585 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/a3/07297917485ee2ca85bc3c8dc6ed85ad3fffcf424047fba62671dba68e97/greenlet-3.5.5-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:be63afcbbccfad3dd95a1ba12ada84dab2ef32031973d80b5b92df67fa763a61", size = 294165 }, + { url = "https://files.pythonhosted.org/packages/db/51/6f732f9314cda54c5fd48a7620c7160f4f286967e8045ad94b9d66ce80b7/greenlet-3.5.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a268024ce2d7d2b04694bf1594058981a9fa663d1df4b762dee499211ed7c1c", size = 613610 }, + { url = "https://files.pythonhosted.org/packages/d8/c0/b27589e25d220289edcd4d582b2b17b83058d1a56d53d971b6ea1a34f10d/greenlet-3.5.5-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35cbb8bf55ace57fbccb4fb8622c4521713acd8691e77f4696d416ea7ca527da", size = 625481 }, + { url = "https://files.pythonhosted.org/packages/39/82/5c873dbb4fb001d22fbbd50e80d4c1b0181ddae106856132160f84b94e88/greenlet-3.5.5-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:abc8bc8d9f935cd685457545b6a53863a877fdc12c2c0f5ee9beee18d9db139c", size = 633329 }, + { url = "https://files.pythonhosted.org/packages/51/2d/f2c928218ac52f26d7a2c188c171d1b7e728b23782cb3347e7b4fce1493a/greenlet-3.5.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cc6df89ec5302337adc9cf096221cbed2510fd444b0e0f1586cf0470740864", size = 624562 }, + { url = "https://files.pythonhosted.org/packages/70/12/f7df98e72a8eb4a7edfec5a08d6d1a4ab53a52c95ba0b2ea6c10b8dd9bd0/greenlet-3.5.5-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:3134291427bb0f3526e9d90311988caf336eb43730e95244997a4fb15f45144f", size = 428145 }, + { url = "https://files.pythonhosted.org/packages/3e/4a/92fc51d5d35912f4f06eec037ba347985defd0be47463a010a325634d9d2/greenlet-3.5.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d9b454c5fc48aeaa7c4337813dbf513a6870468e426438a04d922c6d0fe63db", size = 1584909 }, + { url = "https://files.pythonhosted.org/packages/ac/58/ed98b80ac5738c149a5258544843c45601ade1fd70f61740cdaead6351b3/greenlet-3.5.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03551ed792cb1b4fc0277a0c60dfd8c343894a0ba06fe60dcd22f568b433da39", size = 1651184 }, + { url = "https://files.pythonhosted.org/packages/d8/be/b582ceb80cefdf9d8da34078714e4b12b3d16f509dee0f65e40a5cc8fc7d/greenlet-3.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:ab3df3dffb58bf70564e93a5cec7941e4d9faa5a36cc4234a10d3131afe04f53", size = 323280 }, + { url = "https://files.pythonhosted.org/packages/4d/18/5313c4c58598c38b0373c013e4ff2b3e6d258aaaa338f373335ebecdaddd/greenlet-3.5.5-cp311-cp311-win_arm64.whl", hash = "sha256:2b70a766135540c472ac1393d57c2e1b4a2eb85bf526a1e41e6d096173a8cee5", size = 307785 }, + { url = "https://files.pythonhosted.org/packages/2e/7e/9ecd0285e3153532ae07aeb88063c43c72b4221cf0d4d123b02f3682e3ff/greenlet-3.5.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:49520f0c95a48b42cf55414b8e8479beb274ea70431afc33e3f79903c71f4380", size = 295809 }, + { url = "https://files.pythonhosted.org/packages/35/73/60e4bbcc89252037b18087f2ec16405d5b2d5be42dde191bbf3667e96102/greenlet-3.5.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55272212cbc5f43d1d723725ab931f1939969b7e9523882ca58b55061769d053", size = 611910 }, + { url = "https://files.pythonhosted.org/packages/a4/17/cd5134be659cd4a443e7a61ae670dabec165a814c51162916d637b6dd38e/greenlet-3.5.5-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655bca754a2ef4efcb0eb48a94d3f4593536d0f3d48f8ed44343c01d16a92f95", size = 624198 }, + { url = "https://files.pythonhosted.org/packages/9b/30/87c212b5c684d0e72974f1063b7a9687631e8985902c06e1016542c874e7/greenlet-3.5.5-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ca5d6ae0739e5764f2cfcfaa562ac5a990cbdaedca93251c5e3cf07c362371f", size = 629504 }, + { url = "https://files.pythonhosted.org/packages/78/ac/5c5b959999b6f09c3026b5dfe171575bc3121c5236ce74f495096f25b203/greenlet-3.5.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:147b25a42e5ca5be3d42356e8f608b37af715a1c196e9bf9d1627f3341adfe1d", size = 621439 }, + { url = "https://files.pythonhosted.org/packages/63/2c/eb487fafc9f50ffff2b1e0b697f70fb34bf150821c08ab225aacf5583a7e/greenlet-3.5.5-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:1b5ed9162c0c098e0bbc2cf88a94f433c1b8926f831745252e099e5d83e17759", size = 432462 }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6acf112ed8aee499f25b4d6949820fb02ac950ff9c1f3d793bd5be0599f2/greenlet-3.5.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:27493374cff1d1b7919dc8126547f2aea582737e3046147b434b1e12de56389b", size = 1581342 }, + { url = "https://files.pythonhosted.org/packages/b8/d7/734e5f198888876b42d7616ff6644c075baf6b8a2412deadd6b0e1b8b20c/greenlet-3.5.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:12e2ee66c2aba86133f10fd99d6a8856c6d351ffb7be0e4d52ef2cc5fbb705b2", size = 1645744 }, + { url = "https://files.pythonhosted.org/packages/de/30/1f42b88dc587b5899ee50616ad56ee40cafaf225df4fb829f10183c62a5c/greenlet-3.5.5-cp312-cp312-win_amd64.whl", hash = "sha256:49ddacd36af37735fab103846f4ee4d18a492dde72730d1699c0c8ebe30d9f18", size = 324171 }, + { url = "https://files.pythonhosted.org/packages/76/e5/4dee4d8d2e603fe5fdd7b444e63219f7b9bd852c60c6214511c7157cbe88/greenlet-3.5.5-cp312-cp312-win_arm64.whl", hash = "sha256:5f1b1ff4828cdc1aba4266aff814085d04a1d07959287219af021b838b265d52", size = 308362 }, + { url = "https://files.pythonhosted.org/packages/fb/3d/8cef5f724ec0d4add2af8961d504535ec60c3cca9e464f6d03bdba29d85b/greenlet-3.5.5-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:b79fd2a5bc099b5e744f34c4c9a58954a5f4cb7529fb4b6e8446057d61b6edaa", size = 294730 }, + { url = "https://files.pythonhosted.org/packages/88/4b/8e7aa3f514273aecff30a16ab1bac09ff54cfc7e6860fdd8058c37ff2499/greenlet-3.5.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:634cf15a233a949136879dd388e25d3296e16f3f1e217d2456797b8579ebc6ed", size = 614536 }, + { url = "https://files.pythonhosted.org/packages/85/48/4e95e9dd5a8a397dc6a6345dd7f1935113d0fca4f85e89d3976da9cd988d/greenlet-3.5.5-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:499adea519f748407fc6806d20eedabac2884fd73b9f38d81236e190ba20dfef", size = 626924 }, + { url = "https://files.pythonhosted.org/packages/0e/84/eaa476d6bf3816828d0d70e80dcc36bf30a058233bd889e707e693f6e860/greenlet-3.5.5-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7278591501941bb2456af102bb9cd59aab48c6cfd6e2dd68fa1290bb0c49a42", size = 632726 }, + { url = "https://files.pythonhosted.org/packages/89/5d/398a1c71fa7a277deeb376c999979de6786f08fc2d5747a0b9d6e11738dd/greenlet-3.5.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2eabb980975cba5b93a95f6f69287d05fc05ac955bfd6a320a7c083eeb52c0b0", size = 623906 }, + { url = "https://files.pythonhosted.org/packages/d0/f2/0cc2849ede68579291e9c59b3ab6ec1958f98681cca5b14d8fc75bf674a4/greenlet-3.5.5-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:4dfc7c4470354e7b09184d1a3a985761053a2fd694ddb5b5c80242afc2c8c90b", size = 434966 }, + { url = "https://files.pythonhosted.org/packages/04/1b/745450fc5ea9e0cb17d840d248f284db3363de736d362c7d2d883e3eadba/greenlet-3.5.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03115c2e0a371999bf8ae616aa8d653f96641d4705c457aebaa187276e9f7537", size = 1581430 }, + { url = "https://files.pythonhosted.org/packages/d4/29/d51b296e3191bb15d3d81ec375af1909e4466c0f395d744ed475801798a9/greenlet-3.5.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4441153ffba21b90d3ca89fe3d31f5c093ae6c0bf0cfdfc98f54cde22f95b62e", size = 1645684 }, + { url = "https://files.pythonhosted.org/packages/12/63/369f1a1625e64e9e31df3963c6044056e3fdfa3fa3fdba3c54ffefa6e987/greenlet-3.5.5-cp313-cp313-win_amd64.whl", hash = "sha256:95c5b1f4b3a193f8a0c2de4bfdcb48d119f7f1063941f1de1f2168051b3e52dd", size = 324075 }, + { url = "https://files.pythonhosted.org/packages/45/78/649cb5c09d4d81f6dd1444e75474a7206784743283a21d24171562ac4899/greenlet-3.5.5-cp313-cp313-win_arm64.whl", hash = "sha256:1af90aa4bc129883b340cdd6957a3bc74f60528a4993bbd1f53aaebe1d9981cc", size = 308260 }, + { url = "https://files.pythonhosted.org/packages/7f/8c/080e881fa2be95ff1ddbd6994b2bab3b1a78df3b3fcab39306011764fcc7/greenlet-3.5.5-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d4a389a852e392a6366058651a20fa5ba40d979865aa81bea2ccbdc44805070d", size = 295309 }, + { url = "https://files.pythonhosted.org/packages/25/cc/0ac614e6586c0e42d4cc281a5819150f4f43685744a4c5ff77139286409d/greenlet-3.5.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70b157cd319873e8b544ddc2de158f55bbd0a9b0218c8ce9332039801518e328", size = 661185 }, + { url = "https://files.pythonhosted.org/packages/5e/b9/6808725354be8ad305dfe5172377664fc9642d4fc043be246b3314cf4482/greenlet-3.5.5-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8bdfd1424abcf26832961e766570cae79efdb9599d709088c9cb6ef82b194926", size = 673419 }, + { url = "https://files.pythonhosted.org/packages/eb/52/f005d579acde46c3d1cc3cab1c9f3d5708c8a3006a4120e8cf5da801afe9/greenlet-3.5.5-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d98ef6f92e67c6dbf299dbfd8facc1b0d2d9cedf91e325e73b3d0373fe4309d8", size = 677863 }, + { url = "https://files.pythonhosted.org/packages/42/2e/40c509967da7f254680826a2fa0dd22138ec79946c70b97542d74cde8b43/greenlet-3.5.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:182de51c6b572a705f2fafaab2e783bcf7d2760940229dfe73086cbae037af3e", size = 670822 }, + { url = "https://files.pythonhosted.org/packages/c4/8a/a75f8a2bdcef3c358a3147cdc9db3aa83755f0a038f766ab0bedb66f512c/greenlet-3.5.5-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:159df1942d88e8f784cbb38d6f18bdb365cd11319cfbb3e89623de2b97892d53", size = 480554 }, + { url = "https://files.pythonhosted.org/packages/2d/22/c3c2eee4a8fe191d6d1d183086c56133d646024e3d70bfd414829f64560b/greenlet-3.5.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8fec3f165dfe332e490c3247c0f6c23b0bfc45f06496ad7f00ddb00e3d35e4dc", size = 1628469 }, + { url = "https://files.pythonhosted.org/packages/f7/87/25babd09b94cb1f03e71db815fde463f0262e40cfbd953d58a8d77311351/greenlet-3.5.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c6ce25fee6cabc8bf22cb8b52e642cbb821be5b9aec8094d07ff03378141b8e9", size = 1691952 }, + { url = "https://files.pythonhosted.org/packages/2e/3d/5cc9701117ea4dc0eb7bf1f4f9b7888a6e2e5277ddfae095805ace50f2b6/greenlet-3.5.5-cp314-cp314-win_amd64.whl", hash = "sha256:7dffc5c859fe6059974df1e37d7923d654a83e2ae18fdd616994270e001115e1", size = 327458 }, + { url = "https://files.pythonhosted.org/packages/a7/6b/594fa2de7fae7629168a404a4305d7d7e31a5742c50a801b1839543cb93d/greenlet-3.5.5-cp314-cp314-win_arm64.whl", hash = "sha256:5e2afcfc4d4305dd715809b03da5cbe437c8984f61d8917751eb5fe4aefa3e07", size = 311146 }, + { url = "https://files.pythonhosted.org/packages/24/e0/50cd600b469e5734c72709b6b1838b6bc63f307b573c772c3132d6ecfe92/greenlet-3.5.5-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:0e5a7de979d764aea1f5b6e95cf92b5b37741b9823702041f34b126e7f690277", size = 305471 }, + { url = "https://files.pythonhosted.org/packages/75/a3/77acd66dfc6387b5219b2080806c0cabb73c10eb1bb44b413c40a62015ba/greenlet-3.5.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fef01bd457f11fc158b130ca0027a3c365693280e8e231b65bdaf57999f39f5b", size = 672470 }, + { url = "https://files.pythonhosted.org/packages/b9/71/0d178142dca3ec19f46fb2212ae73d30ad53b9d548dc64804086033a7089/greenlet-3.5.5-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5173a72310725a74afc82c164f0e52cb8ad0de62f2bb623f24f6c0cc07d80272", size = 679973 }, + { url = "https://files.pythonhosted.org/packages/aa/ac/0d7887aa4bbfc9eba075cc428244dfc96f623478454d5ec81180d0d6bd5a/greenlet-3.5.5-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5e9ec2e7c98e895fcea0c5cc57b2606cf86ece6d0a56578f3eb225e2af4f0387", size = 681587 }, + { url = "https://files.pythonhosted.org/packages/6e/31/46eb8567302eaf787abf88d09df014e14ae3baf460af1b8b0efdbd3efcd5/greenlet-3.5.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44f08341873200ba8a60a8bc14ace3d91f1754f7fa7bc66157714a8cd420a476", size = 676634 }, + { url = "https://files.pythonhosted.org/packages/4f/18/8d58ba1c429b0383e3219a3d0e0bba241d0444d8ed05b73349953c7d7c7b/greenlet-3.5.5-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:102817506f6090b5176c746a82603341a549b40e5c3d5b72a4c672228a918c41", size = 510175 }, + { url = "https://files.pythonhosted.org/packages/a3/e9/b88bbf5b29970cb84172dc2c32aa3e5e579ceb94c808e81c826454138850/greenlet-3.5.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d246c0db9a2513cd45f019ba178ea4d4d4705bd210ee465e2c15d76a1ab13874", size = 1637320 }, + { url = "https://files.pythonhosted.org/packages/6d/8c/7631ed29cc6f0392f11830076e172ce4885e70b0bc2c1bce1731176d4b4e/greenlet-3.5.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:72507285b5caa1d17904a3f7c322ca780823a54170a0e04ec3f37bcc60d4db71", size = 1697412 }, + { url = "https://files.pythonhosted.org/packages/da/0f/f7dd935f9c4cb1be49098770587f54d8a78518e55c89bce86c4fb4109057/greenlet-3.5.5-cp314-cp314t-win_amd64.whl", hash = "sha256:7805655781fb8f28a55d05fe57ed61f5f10f1892fb587673e3bb5264f28041f0", size = 331514 }, + { url = "https://files.pythonhosted.org/packages/b7/e5/681b01f8fbc1b55232822f99e8f8afeb78a55a7c76a7bf9dbdc7ccb03a6d/greenlet-3.5.5-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:c0db80fcd5b8aece93f66c64f78a786bbb6b96c5fe63ef5a5a4581ecf8bab206", size = 295975 }, + { url = "https://files.pythonhosted.org/packages/11/f2/69b488cd9e7267bf4b0fe8cdebf25d8d6df680d21bdf41150d23e23d6652/greenlet-3.5.5-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b241c32f912ada659808d68e308c568baf577eebf757d15471472de0c18cfad", size = 666823 }, + { url = "https://files.pythonhosted.org/packages/84/d4/d5bc2fdebbdda0c94555925ba79948b8395d75a7f6a36cc85dce5bab9f11/greenlet-3.5.5-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ef6a08349401d8eaf3cb12688ac8557de95788556b8631ef17555a4a173022c0", size = 677613 }, + { url = "https://files.pythonhosted.org/packages/65/53/4e13642efc4d7ad6554ecb2242a5be42666b2e1a067323e88dfc0124a04b/greenlet-3.5.5-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37faa97daccb6d9f4c2141ce3118d023c3c5506864a7d8bdf726f665018c1f76", size = 681436 }, + { url = "https://files.pythonhosted.org/packages/bd/93/542d8a3a90f3b35c6ad8bf7e56a03010287f2cafa289a5b7985b5207db39/greenlet-3.5.5-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2e3d061b8e13aec2f0441689b3c71b244a20e5d274a52cb0f7e31bd1d139552", size = 675930 }, + { url = "https://files.pythonhosted.org/packages/cd/32/188447c9a468d6977d2989397226b0c6b65ab6f4cf943f931643328512fc/greenlet-3.5.5-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:b18007dc2473a7942fd157366b55f01da6fed7ce85318591005b419e0a439474", size = 487404 }, + { url = "https://files.pythonhosted.org/packages/52/b5/89c9f2e8460d71101037d47a1feed11928615a5edd42370be290e0657eeb/greenlet-3.5.5-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:9ab5f5b93655e77fe0d6c2dfd22b5eac751bb1f876d8ec21761b7c1fb9266007", size = 1633878 }, + { url = "https://files.pythonhosted.org/packages/b8/60/297de93f3b02ac78a5e04d32bb8bbe3080f4a73d8ed95016561463b70618/greenlet-3.5.5-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f0e5a21bd4452a88cf032fc43c4a5b307ab1380eacb63b5988f9c0317885e773", size = 1696597 }, + { url = "https://files.pythonhosted.org/packages/18/25/54c6eaff4f337fb670215e89eb2d00d9499487b658e709d4b477be4a342e/greenlet-3.5.5-cp315-cp315-win_amd64.whl", hash = "sha256:469dbb0a78625642f4a626cfd0c6e8bccc0385b5e49189b6308bbe849ec88a8e", size = 327700 }, + { url = "https://files.pythonhosted.org/packages/67/67/857e88a36301caa0e029870132c2478bd55d896630321432afab03a3115f/greenlet-3.5.5-cp315-cp315-win_arm64.whl", hash = "sha256:2d57406c3efd32d7a81e17a674314e8bd00792cdab49ea3228a49aa1bfb2e769", size = 311750 }, + { url = "https://files.pythonhosted.org/packages/10/e2/3144c0a116067ac1e30457b0139a94d60d1d36a86e015de68e9ac87cb3bc/greenlet-3.5.5-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:68184dfcf50ccaa8e864770fe0633a7e27250ea9329f8192ef47ee9ecfd78e1c", size = 306387 }, + { url = "https://files.pythonhosted.org/packages/5c/a1/cb4223a7e9b9f43b8807e8eb212358bfe2dfaa174a9ea2889eb1714dcba2/greenlet-3.5.5-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ec0dc0e59dc9c61af5c47348365ccbbd7addfafe0a93b00336ff3da2907bdc6", size = 676472 }, + { url = "https://files.pythonhosted.org/packages/9e/cd/a154b4498e5d8f12ada291cfb3b8d596eadde2177f5bf09a9be699d2a446/greenlet-3.5.5-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e604f58e35833fc46ef20302bcb314dddbfd3fcf33a4f936216d51dd678d63ae", size = 684238 }, + { url = "https://files.pythonhosted.org/packages/ce/f4/e450a68a152f819491d8c7df6a8254e761d87e6a78759268961f8c5bd4dd/greenlet-3.5.5-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2888a3a38bc5ee5bb6c438372197152e815837e4fab7ed7a1f86ef18ffd58ad1", size = 686022 }, + { url = "https://files.pythonhosted.org/packages/bf/bb/b0031d260c2968a3c87deebc51d80c64e499377f993aafe06ee3b7488cc2/greenlet-3.5.5-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40239b5384f96da3963585cc6d7eaa9b56f8ae67e8d92cc82dd9e202fc847de3", size = 681246 }, + { url = "https://files.pythonhosted.org/packages/18/23/17e63d6bf3b9c9b9dbea981b7f643a71f79603bdfb4f1c3a9cf353e22aed/greenlet-3.5.5-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:1e8d9391fe77f15649589a907cef972dbbd6352ef7ff7dc0492f658c0c26495f", size = 516951 }, + { url = "https://files.pythonhosted.org/packages/9a/07/da554b71ab88e649da146e1065d86a48a5c5d92e50ab74ef41b504aa7f56/greenlet-3.5.5-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:a1eaccf5c3a1d3e46dead602c72e6836731e8e245c9de6a27764567b6b62d4c0", size = 1642735 }, + { url = "https://files.pythonhosted.org/packages/78/76/26a3782a051677668af9d92beaa47cd87ba9dd5072f762961144a03dd4c6/greenlet-3.5.5-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:19e4e026fe20691f333b8eb1a3bc9625eceba8c3f9d62ec5a6f8581afbc6b5a5", size = 1700925 }, + { url = "https://files.pythonhosted.org/packages/28/d9/fe7baf4190c2ae71f267efb9de21b3172bb35bc0ed1ef53dd6027d658e33/greenlet-3.5.5-cp315-cp315t-win_amd64.whl", hash = "sha256:712aee154f648bde84634654bb38bb78c69ac640c37a45c9effed800735049d8", size = 331829 }, + { url = "https://files.pythonhosted.org/packages/df/af/419a4e383bd600858a9b67e9b280a60fdc383ee3f2fe5b6c0c1ef04e74d1/greenlet-3.5.5-cp315-cp315t-win_arm64.whl", hash = "sha256:7f049911ee81a16a03c33d5450d8d5867d27f596ca5fb201b86f4524e874468b", size = 315093 }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, +] + +[[package]] +name = "hf-xet" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef", size = 920527 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/62/3c062f593bd92ef4e77a0ef39541e3d82a0a1d3947c8a777a02a13a27828/hf_xet-1.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:70cbb9c896901600128cb9b6f06e132954fbede1db30f31f7c6c63f84cb7c31d", size = 4074584 }, + { url = "https://files.pythonhosted.org/packages/bb/1e/c0ad437dd267a8e435bef594acf781bbc3874ff0b6435b4962d03ecf7cc4/hf_xet-1.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23379c2f9ec8696d952b16414a2bae72cad86a52df869b050698ba60f538c675", size = 3867381 }, + { url = "https://files.pythonhosted.org/packages/d5/ee/7c0d7b6ab336167531b1c30af2af003f054af4c749becbd7209ae33a77c3/hf_xet-1.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f7278c05c22fd60cb436cda1269649b3e81db65ecdc8496e5e164aa4143e7b", size = 4453982 }, + { url = "https://files.pythonhosted.org/packages/63/06/ad8eab1c9525246650cbaa821caa3cdbaca734ab1a5b8c91bea09cbd8d69/hf_xet-1.6.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:948f15d3a9545cfe5932f6bd8b440f6ae630aee108f14b7bd6c561f7c2dcc522", size = 4249445 }, + { url = "https://files.pythonhosted.org/packages/d8/26/1eee8aedb0dafc1ab9717dc9ac602cde33361b232dc06803f1f6ed18b58c/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5153e6bb103ad49d6ea9f1b2e230db5a2ea32551ad09a706d2f61d7c7c80d80e", size = 4451099 }, + { url = "https://files.pythonhosted.org/packages/67/57/0b88af1f194ab6c9c650547d9cc06bfeaab836ae4dcdb331676bfb8be95a/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:35cec30d75c6f9eb9c16a77cef68e85a103b72e24d4b473714ec9ff06428bab9", size = 4664712 }, + { url = "https://files.pythonhosted.org/packages/53/a0/26b717a9d1840e8abf48dcec64b5ed8fbe472671d38ad28d30e147132b33/hf_xet-1.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5789835d7c6bc9436962853192082374297fb72d7eff7e7762ec25ceb7e25338", size = 4025906 }, + { url = "https://files.pythonhosted.org/packages/49/f6/4a9966633c6fef83af997e2cff68ec1963676d412bdfd096df2a93b8e185/hf_xet-1.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:75765820ce4700db3750c94acc8fe27c5fae4c9ec000a0dbac3ca082acf97765", size = 3849221 }, + { url = "https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d", size = 4071729 }, + { url = "https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a", size = 3876287 }, + { url = "https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f", size = 4464663 }, + { url = "https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7", size = 4262538 }, + { url = "https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb", size = 4460520 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c", size = 4675937 }, + { url = "https://files.pythonhosted.org/packages/98/b7/8c59a66d15205024662f1d66968136f13893f96df1ddc5087e2e281fc95f/hf_xet-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b", size = 4033128 }, + { url = "https://files.pythonhosted.org/packages/73/63/ca511b6f802f28cf3489b280fe77475bcca8de85e81a6299d7916b5b5555/hf_xet-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3", size = 3859359 }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "h11", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, +] + +[[package]] +name = "httpcore2" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11", marker = "(python_full_version < '3.15' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten') or (platform_system != 'Linux' and sys_platform != 'emscripten') or sys_platform == 'darwin'" }, + { name = "truststore", marker = "(python_full_version < '3.15' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten') or (platform_system != 'Linux' and sys_platform != 'emscripten') or sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/74/d370e55600d9bcfa0d9794b0166126d49291a3d2b20c268fc98c453a4948/httpcore2-2.12.0-py3-none-any.whl", hash = "sha256:7e04258ce01013d7d615e5b910a3b27fac937d7a95038227e79652b4ba3b4ceb", size = 83074 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "certifi", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "httpcore", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "idna", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[[package]] +name = "httpx2" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "(python_full_version < '3.15' and sys_platform != 'emscripten') or (platform_system != 'Linux' and sys_platform != 'emscripten') or sys_platform == 'darwin'" }, + { name = "httpcore2", marker = "(python_full_version < '3.15' and sys_platform != 'emscripten') or (platform_system != 'Linux' and sys_platform != 'emscripten') or sys_platform == 'darwin'" }, + { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" }, + { name = "idna", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "truststore", marker = "(python_full_version < '3.15' and sys_platform != 'emscripten') or (platform_system != 'Linux' and sys_platform != 'emscripten') or sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/f8/579a8b51e42e38ee32647df9f08aa25643ae788e275cc625b199829c4671/httpx2-2.12.0.tar.gz", hash = "sha256:7631fe9887a8a2275f4a2540e053aa670fcc50742864a9ae7c66e609fdcf12cf", size = 100040 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/95/411ba65569158e862368917aaf56597f3e5fa3b91b0502919638465a08f3/httpx2-2.12.0-py3-none-any.whl", hash = "sha256:cc8b6eecb8661c146b8f89a60e97456ee086e91a784ed31ac450c3a9e613dd36", size = 95427 }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382 }, +] + +[[package]] +name = "huggingface-hub" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "filelock", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "fsspec", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "hf-xet", marker = "(python_full_version < '3.15' and platform_machine == 'AMD64') or (python_full_version < '3.15' and platform_machine == 'aarch64') or (python_full_version < '3.15' and platform_machine == 'amd64') or (python_full_version < '3.15' and platform_machine == 'arm64') or (python_full_version < '3.15' and platform_machine == 'x86_64') or (platform_machine == 'AMD64' and platform_system != 'Linux') or (platform_machine == 'aarch64' and platform_system != 'Linux') or (platform_machine == 'amd64' and platform_system != 'Linux') or (platform_machine == 'arm64' and platform_system != 'Linux') or (platform_machine == 'x86_64' and platform_system != 'Linux') or (platform_machine == 'AMD64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'darwin') or (platform_machine == 'amd64' and sys_platform == 'darwin') or (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'AMD64' and sys_platform == 'emscripten') or (platform_machine == 'aarch64' and sys_platform == 'emscripten') or (platform_machine == 'amd64' and sys_platform == 'emscripten') or (platform_machine == 'arm64' and sys_platform == 'emscripten') or (platform_machine == 'x86_64' and sys_platform == 'emscripten')" }, + { name = "httpx", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "packaging", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pyyaml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tqdm", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/35/42316e8f6908b6d21bc8df017cc6efba94fb5edbf99b64e28dd142325e20/huggingface_hub-1.29.0.tar.gz", hash = "sha256:6ebb385a581435325cf6d5c5b233d5d4bc91175834d99fd65dae14379b36e9ad", size = 963121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/a5/47c2ea9b228ccbcba8467e9a64823146e8ebbad29855e591d8f5eedcc9c7/huggingface_hub-1.29.0-py3-none-any.whl", hash = "sha256:b00f7782afc14db4bc6572763810a635bdfbab8623d957bfb553bd18e03852cd", size = 795768 }, +] + +[[package]] +name = "idna" +version = "3.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550 }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "jiter" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/3f/fae6cc967d120ec89e31c5418a51176d8278b3087fbb384a9176754f353c/jiter-0.16.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:67fddeda1688f0cce2d2ae83ccf8a80f79936f2d2997d6cc2261f82fdb54a4d3", size = 309289 }, + { url = "https://files.pythonhosted.org/packages/c8/e3/97c6c3562c077f6247d6e6ce5c82562500b6316c0d928e97e106b7a1321a/jiter-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c90c0f63df322be920eda6ce622e3083d8906ba267f8220fe7873213b8b4430e", size = 315181 }, + { url = "https://files.pythonhosted.org/packages/7b/89/d8d073f8aa2667e46c6c0873f86fe4a512bba4293cc730f626a076211a62/jiter-0.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64c0203212098470032aabcde9356fc168f377aade3e43def61dfe17e92f2037", size = 340939 }, + { url = "https://files.pythonhosted.org/packages/87/c9/db4fda3ed73fb864139305e935e5b8b38a5a24692a5a9dd356c22f1b9c8d/jiter-0.16.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12288303c9844e61e1651d02a9a6f6633e47d39f897d6991d1427161ce6b746e", size = 364932 }, + { url = "https://files.pythonhosted.org/packages/a2/74/52b5e86241057f52ddd7c9a580f90effb51f9d06239f6fc612279b91a838/jiter-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cf109d010b4b05a105afb3d43be36a21322d345ad3111e13d15f680afef0e5b", size = 461132 }, + { url = "https://files.pythonhosted.org/packages/a9/87/544a700f7447c1f31c5d7833821a4daa5683165c2d5a094fbf5b5800c3dc/jiter-0.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62c1b7fe1f77925acf5af68b6140b8810fa87dfd4dc0a9c8568ec2fa2a10429c", size = 374857 }, + { url = "https://files.pythonhosted.org/packages/40/cd/0fcc3f7d39183674d5bfa9ec640faaeb506c60be7c8f94625dfba366e37c/jiter-0.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8597d23c87f59294f83bcb6229b9ed1fccee13dbba967b46930d2f1759466fee", size = 347053 }, + { url = "https://files.pythonhosted.org/packages/5c/ae/c7e64e7932ad597fa395b61440b249ada6366716e25c6e08dd2afbd021e6/jiter-0.16.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3126a5dbad56401989ac769aca0cb56005bfb3e2366eea0ca99d1a91c3c1ee03", size = 356153 }, + { url = "https://files.pythonhosted.org/packages/d4/1c/1c719044f14da814e1a060191ab19b96f3e99207bc5b4bfc6d6be34b3f80/jiter-0.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c4b4717bdb35ae456f831a6b08d01880fff399887a6bbc526a583a406e484eea", size = 393956 }, + { url = "https://files.pythonhosted.org/packages/3b/dc/7b2f303a2847207e265503853a2d964a55354cffd62a5f2936c155486798/jiter-0.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:adff21bc78edfe086c15eb495b900306076de378dc2337c132401fc39bd79c91", size = 521081 }, + { url = "https://files.pythonhosted.org/packages/c2/5f/501cf6e1e09caeb420195179ffc6f62aca603f1220ec53fd80d0d70b3e56/jiter-0.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dab907db06fc593645e73109acf4581ba5b548897d28b9348dc41ddc8343b2d3", size = 552085 }, + { url = "https://files.pythonhosted.org/packages/79/54/aa5be86520113b79455c3877f3d1f07a348098df4083ba3688e9537e52dd/jiter-0.16.0-cp311-cp311-win32.whl", hash = "sha256:560b2cf3fb03240cd34f27409a238547488708f05b7c3924f571a60422251ec7", size = 206755 }, + { url = "https://files.pythonhosted.org/packages/64/ec/2feb893eb330bd69b413866f4d5daada33c3962f1c6f270c91ca2d87fdf9/jiter-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:e431cfc9caf44c1d5459ff77d4e64cbf85fddb6a35dad836a15c6a9ec23087c1", size = 199155 }, + { url = "https://files.pythonhosted.org/packages/b9/9c/ca040d94415048a3666fc237774df8151c96f8d2b661cbe3b184acc95876/jiter-0.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:2a8e9e39cf083016137aa5cadafe3188adc2ba6ba1fbf1e5d18889ad3e9ad056", size = 194403 }, + { url = "https://files.pythonhosted.org/packages/83/2b/52ace16ed031354f0539749a49e4bf33797d82bea5137910835fa4b09793/jiter-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a", size = 306943 }, + { url = "https://files.pythonhosted.org/packages/94/2e/34957c2c1b661c252ba9bcc60ae0bddc27e0f7202c6073326a13c5390eec/jiter-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9", size = 307779 }, + { url = "https://files.pythonhosted.org/packages/88/6c/59bd309cab4460c54cf1079f3eb7fe7af6a4c895c5c957a53378693bad2b/jiter-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72", size = 335826 }, + { url = "https://files.pythonhosted.org/packages/3b/8c/f5ef7b65f0df47afa16596969defb281ebb86e96df346d62be6fd853d620/jiter-0.16.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4444a83f946605990c98f625cdd3d2725bfb818158760c5748c653170a20e0e", size = 362573 }, + { url = "https://files.pythonhosted.org/packages/2b/0b/ace4354da061ee38844a0c27dc2c21eecd27aea119e8da324bea987522d0/jiter-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a23f0e4f957e1be65752d2dfac9a5a06b1917af8dc85deb639c3b9d02e31290", size = 457979 }, + { url = "https://files.pythonhosted.org/packages/55/40/c0253d3772eb9dcd8e6606ee9b2d53ec8e5b814589c47f140aa585f21eaa/jiter-0.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c22a488f7b9218e245a0025a9ba6b100e2e54700831cf4cf16833a27fba3ad01", size = 372302 }, + { url = "https://files.pythonhosted.org/packages/a8/d2/4839422241aa12860ce597b20068727094ba0bc480723c74924ca5bad483/jiter-0.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46add52f4ad47a08bfb1219f3e673da972191489a33016edefdb5ea55bfa8c48", size = 343805 }, + { url = "https://files.pythonhosted.org/packages/e2/59/e196888a05befdda7dbe299b722d56f2f6eec65402bc34c0a3306d595feb/jiter-0.16.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9c8a956fd72c2cf1e730d01ea080341f13aa0a97a4a33b51abebe725b7ae9ca9", size = 351107 }, + { url = "https://files.pythonhosted.org/packages/ec/74/4cd9e0fca65232136400354b630fbfcd2de634e22ccbb96567725981b548/jiter-0.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:561926e0573ffe4a32498420a76d64b16c513e1ab413b9d28158a8764ac701e5", size = 388441 }, + { url = "https://files.pythonhosted.org/packages/d9/8c/554691e48bc711299c0a293dd8a6179e24b2d66a54dc295421fcf64569c0/jiter-0.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:44d019fa8cdaf89bf29c71b39e3712143fdd0ac76725c6ef954f9957a5ea8730", size = 516354 }, + { url = "https://files.pythonhosted.org/packages/a4/cb/01e9d69dc2cc6759d4f91e230b34489c4fdb2518992650633f9e20bece89/jiter-0.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0df91907609837f33341b8e6fe73b95991fdaa57caf1a0fbd343dffe826f386f", size = 547880 }, + { url = "https://files.pythonhosted.org/packages/79/70/2953195f1c6ad00f49fa67e13df7e60acb3dd4f387101bc15abccddd905e/jiter-0.16.0-cp312-cp312-win32.whl", hash = "sha256:51d7b836acb0108d7c77df1742332cac2a1fa04a74d6dacec46e7091f0e91274", size = 203473 }, + { url = "https://files.pythonhosted.org/packages/2d/05/2909a8b10699a4d560f8c502b6b2c5f3991b682b1922c1eedda242b225bd/jiter-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1878349266f8ee36ecb1375cc5ba2f115f35fd9f0a1a4119e725e379126647f7", size = 196905 }, + { url = "https://files.pythonhosted.org/packages/e9/a9/6b82bb1c8d7790d602489b967b982a909e5d092875a6c2ade96444c8dfc5/jiter-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:2ed5738ae4af18271a51a528b8811b0cbfa4a1858de9d83359e4169855d6a331", size = 190618 }, + { url = "https://files.pythonhosted.org/packages/91/c0/555fc60473d30d66894ba825e63615e3be7524fac23858356afa7a38906c/jiter-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195", size = 306203 }, + { url = "https://files.pythonhosted.org/packages/d0/2b/c3eaf16f5d7c9bad66ea32f40a95bd169b29a91217fcc7f081375157e99c/jiter-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09", size = 306489 }, + { url = "https://files.pythonhosted.org/packages/96/3f/02fdfc6705cad96127d883af5c34e4867f554f29ec7705ec1a46156400a9/jiter-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536", size = 335453 }, + { url = "https://files.pythonhosted.org/packages/b2/a6/e4bda5920d4b0d7c5dfb7174ce4a6b2e4d3e11c9162c452ef0eab4cdbdbd/jiter-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450", size = 361625 }, + { url = "https://files.pythonhosted.org/packages/b7/97/4e6b59b2c6e55cbb3e183595f81ad65dcfb21c915fee5e19e335df21bc55/jiter-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e", size = 456958 }, + { url = "https://files.pythonhosted.org/packages/15/e0/97e9557686d2f94f4b93786eccb7eed28e9228ad132ea8237f44727314a7/jiter-0.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8", size = 372017 }, + { url = "https://files.pythonhosted.org/packages/0f/94/db768b6938e0df35c86beeba3dfbbb025c9ee5c19e1aa271f2396e50864d/jiter-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026", size = 343320 }, + { url = "https://files.pythonhosted.org/packages/c1/d6/5a59d938244a30735fe62d9433fd325f9021ea29d89780ea4596ea93bc89/jiter-0.16.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053", size = 350520 }, + { url = "https://files.pythonhosted.org/packages/67/f8/c4a857f49c9af125f6bbcac7e3eee7f7978ed89682833062e2dbf62576b1/jiter-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e", size = 387550 }, + { url = "https://files.pythonhosted.org/packages/8b/d6/5fbc2f7d6b67b754caa61a993a2e626e815dec47ffc2f9e35f01adfebec7/jiter-0.16.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb", size = 515424 }, + { url = "https://files.pythonhosted.org/packages/ed/54/284f0164b64a5fed915fea6ba7e9ba9b3d8d37c67d59cf2e3bb99d45cdfe/jiter-0.16.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84", size = 546981 }, + { url = "https://files.pythonhosted.org/packages/13/c5/2a467585a576594384e1d2c43e1224deaafc085f24e243529cf98beef8e1/jiter-0.16.0-cp313-cp313-win32.whl", hash = "sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e", size = 202853 }, + { url = "https://files.pythonhosted.org/packages/88/6a/de61d04b9eec69c71719968d2f716532a3bc121170c44a39e14979c6be81/jiter-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd", size = 196160 }, + { url = "https://files.pythonhosted.org/packages/19/4b/b390ed59bafb3f31d008d1218578f10327714484b334439947f7e5b11e7f/jiter-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a", size = 189862 }, + { url = "https://files.pythonhosted.org/packages/a7/89/bc4f1b57d5da938fd344a466396541e586d161320d70bffd929aaafcd8f4/jiter-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b2c61484666ad42726029af0c00ef4541f0f3b5cdc550221f56c2343208018ee", size = 308239 }, + { url = "https://files.pythonhosted.org/packages/65/7a/c415453e5213001bf3b411ff65dec3d303b0e76a4a2cfea9768cd4960994/jiter-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63efadc657488f45db1c676d81e704cac2abf3fdb892def1faea61db053127e2", size = 308928 }, + { url = "https://files.pythonhosted.org/packages/11/fc/1f4fb7ebf9a724c7741994f4aae18fba1e2f3133df14521a79194952c34a/jiter-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf0d73f50e7b6935677854f6e8e31d499ca7064dd24734f703e060f5b237d883", size = 336998 }, + { url = "https://files.pythonhosted.org/packages/a0/8d/72cadaac05ccfa7cc3a0a2232862e6c72443ca40cf300ba8b57f9f18b69b/jiter-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3ea07d9bc8e7d03a9fbc051295462e6dbc295b894fd72457c3136e3e43d898", size = 362112 }, + { url = "https://files.pythonhosted.org/packages/58/4a/c4b0d5f651fda90a24ffce9f8d56cde462a2e09d31ae3de3c68cef34c04e/jiter-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26798522707abb47d767db536e4148ceac1b14446bf028ee85e579a2e043cfe5", size = 459807 }, + { url = "https://files.pythonhosted.org/packages/80/58/ef77879ea9aa56b50824edc5a445e226422c7a8d211f3fd2a56bcb9493cf/jiter-0.16.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc837c1b9631be10abfe0191537fe8009838204cec7e44827401ace390ddb567", size = 373181 }, + { url = "https://files.pythonhosted.org/packages/49/2e/ffbc3f254e4d8a66da3062c624a7df4b7c2b2cf9e1fe43cf394b3e104041/jiter-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49060fd70737fad59d33ba9dcc0d83247dc9e77187de26053a19c16c9f32bd69", size = 344927 }, + { url = "https://files.pythonhosted.org/packages/9a/f6/0be5dc6d64a89f80aa8fec984f94dedb2973e251edcae55841d60786d578/jiter-0.16.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:adbb8edeadd431bc4477879d5d371ece7cb1334486584e0f252656dd7ffada29", size = 352754 }, + { url = "https://files.pythonhosted.org/packages/da/6e/7d31243b3b91cd261dd19e9d3557fc3251a80883d3d8049c86174e7ab7af/jiter-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:31aaee5b80f672c1dc21272bcfb9cbdcfc1ea04ff50f00ed5af500b80c44fa93", size = 390553 }, + { url = "https://files.pythonhosted.org/packages/25/33/51ae371fde3c88897520f62b4d5f8b27ad7103e2bb10812ff52195609853/jiter-0.16.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6722bcef4ffc86c835574b1b2fac6b33b9fb4a889c781e67950e891591f3c55a", size = 516900 }, + { url = "https://files.pythonhosted.org/packages/a0/45/6449b3d123ea439ba79507c657288f461d55049e7bcbdc2cf8eb8210f491/jiter-0.16.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5ab4f50ff971b611d656554ea10b75f80097392c827bc32923c6eeb6386c8b00", size = 548754 }, + { url = "https://files.pythonhosted.org/packages/9b/e7/fd2fb11ae3e2649333da3aa170d04d7b3000bbdc3b270f6513382fdf4e04/jiter-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:710cc51d4ebdcd3c1f70b232c1db1ea1344a075770422bbd4bede5708335acbe", size = 122381 }, + { url = "https://files.pythonhosted.org/packages/26/80/f0b147a62c315a164ed2168908286ca302310824c218d3aae52b06c0c9a9/jiter-0.16.0-cp314-cp314-win32.whl", hash = "sha256:57b37fc887a32d44798e4d8ebfa7c9683ff3da1d5bf38f08d1bb3573ccb39106", size = 204578 }, + { url = "https://files.pythonhosted.org/packages/5e/e6/4758a14304b4523a6f5adb2419340086aa3593bd4327c2b25b5948a90548/jiter-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbd18dd5e2df96b580487b5745adf57ef64ad89ba2d9662fc3c19386acce7db8", size = 198154 }, + { url = "https://files.pythonhosted.org/packages/26/be/41fa54a2e7ea41d6c99f1dc5b1f0fd4cb474680304b5d268dd518e81da3a/jiter-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32d2027a9fa67f109ff245a3252ece3ccc32cc56703e1deab6cc846a59e0585", size = 191458 }, + { url = "https://files.pythonhosted.org/packages/81/6b/59127338b86d9fe4d99418f5a15118bea778103ee0fe9d9dd7e0af174e95/jiter-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2577196f4474ef3fc4779a088a23b0897bbf86f9ea3679c372d45b8383b43207", size = 316739 }, + { url = "https://files.pythonhosted.org/packages/2d/95/49461034d5388196d3dabf98748935f017b7785d8f3f5349f834bcc4ed0d/jiter-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e89e008a93c01104161c75b4988e58716b01d62307ebfe161e52a56d2a818", size = 340911 }, + { url = "https://files.pythonhosted.org/packages/cd/97/a4369f2fb82cb3dda13b98622f31249b2e014b223fe64ee534413ad72294/jiter-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2e9efbe042210df657bade597f66d6d75723e3d8f45a12ea6d8167ff8bbce3", size = 361747 }, + { url = "https://files.pythonhosted.org/packages/28/51/49b6ed456261646e1906016a6760367a28aacd3c24805e4e5fe64116c1db/jiter-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f4d9e473a5ce7d27fef8b848df4dc16e283893d3f53b4a585e72c9595f3c284", size = 460225 }, + { url = "https://files.pythonhosted.org/packages/33/b5/5689aff4f66c5b60be63106e591dbfcba2190df97d2c9c7cf052361ddb98/jiter-0.16.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d30a4a1c87713060c8d1cc59a7b6c8fb6b8ef0a6900368014c76c87922a2929", size = 373169 }, + { url = "https://files.pythonhosted.org/packages/a2/96/3ae1b85ee0d6d6cab254fb7f8da018272b932bbf2d69b07e98aa2a96c746/jiter-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae96332410f866e5900d809298b1ed82735932986c672495f9701daacd80620", size = 350332 }, + { url = "https://files.pythonhosted.org/packages/15/32/c99d7bafd78986556c95bf60ce84c6cc98786eac56066c12d7f828bb6747/jiter-0.16.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:da3d7ec75dc83bb18bca888b5edfae0656a26849056c59e05a7728badd17e7af", size = 353377 }, + { url = "https://files.pythonhosted.org/packages/0e/4b/f99a8e571287c3dec766bcc18528bbe8e8fb5365522ab5e6d64c93e87066/jiter-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6162b77d49a9939229df666dfa8af3e656b6701b54c4c84966d740e189264e", size = 387746 }, + { url = "https://files.pythonhosted.org/packages/75/69/c78a5b3f71040e34eb5917df26fb7ae9a2174cad1ccbf277512507c53a6e/jiter-0.16.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:63ffdbdae7d4499f4cda14eadc12ddcabef0fc0c081191bdc2247489cb698077", size = 517292 }, + { url = "https://files.pythonhosted.org/packages/c2/f7/095b38eda4c70d03651c403f29a5590f16d12ddc5d544aac9f9cddf72277/jiter-0.16.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a111256a7193bea0759267b10385e5870949c239ed7b6ddbaaf57573edb38734", size = 549259 }, + { url = "https://files.pythonhosted.org/packages/2e/c5/6a0207d90e5f656d95af98ebd0934f382d37674416f215aeda2ff8063e51/jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf", size = 206523 }, + { url = "https://files.pythonhosted.org/packages/a5/31/c757d5f30a8980fd945ce7b98be10be9e4ff59c7c42f5fd86804c2e87db8/jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db", size = 200366 }, + { url = "https://files.pythonhosted.org/packages/7c/a2/d88de6d313d734a544a7901353ad5db67cb38dcfcd91713b7979dafc345d/jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce", size = 190516 }, + { url = "https://files.pythonhosted.org/packages/06/d3/8e278946d43eeca2585b4dd0834a887cd71136329b837f3a16ed86a8b4b0/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:850ccb1d7eedb4200f4014b1c0e8a577de114fc3cd88faad646dcc9bc4bb12ad", size = 304518 }, + { url = "https://files.pythonhosted.org/packages/72/43/28d4ef495028bf0506a413d4db3f4eb3e7288a382e0f065f306a17bbeb5e/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:e34e97bda77eb63242a410243c071e28ac7e0d8c0948c5ee658498690a4b2f2f", size = 310207 }, + { url = "https://files.pythonhosted.org/packages/e0/ca/c366b1012da1d640de975d9683acd44e4d150d9068845d0ca2610435253f/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7dc85ea77d4abbae8bad0d3538678aedee75bceec4e2f6c8dfb1c74772e5aa5", size = 342771 }, + { url = "https://files.pythonhosted.org/packages/16/52/50cc4056fc1ae02e7154704e7ecc89df0afb8300222cfe8a52d3f67e4730/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17ca7fae79f6d99cd9a042b75f917eaada7b895cfc7dd2ee3a16089dcaec7a85", size = 346468 }, + { url = "https://files.pythonhosted.org/packages/98/ab/664fd8c4be028b2bedd3d2ff08769c4ede23d0dbc87a77c62384a0515b5d/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127", size = 303106 }, + { url = "https://files.pythonhosted.org/packages/1a/07/421f1d5b65493a76e16027b848aba6a7d28073ae75944fa4289cc914d39f/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3", size = 304658 }, + { url = "https://files.pythonhosted.org/packages/0a/db/bba1155f01a01c3c37a89425d571da751bbedf5c54247b831a04cb971798/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702", size = 339719 }, + { url = "https://files.pythonhosted.org/packages/78/f7/18a1afcd64f35314b68c1f23afcd9994d0bc13e65cc77517afff4e83986d/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d613743df53199b1aa256a7d328340da6d7078aac7705a7db9d7a791e9cfd2", size = 343885 }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419 }, +] + +[[package]] +name = "jsonlines" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/87/bcda8e46c88d0e34cad2f09ee2d0c7f5957bccdb9791b0b934ec84d84be4/jsonlines-4.0.0.tar.gz", hash = "sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74", size = 11359 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl", hash = "sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55", size = 8701 }, +] + +[[package]] +name = "jsonref" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425 }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "jsonschema-specifications", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "referencing", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "rpds-py", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437 }, +] + +[[package]] +name = "latex2mathml" +version = "3.81.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/62/35bb816c5c19d4d0cde5bdfb82ebb996306243d5f94e03f201658c629960/latex2mathml-3.81.0.tar.gz", hash = "sha256:4b959cdc3cac8686bc0e3e5aece8127dfb1b81ca1241bed8e00ef31b82bb4022", size = 77584 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/b1/c488b530994c4f68e46efa99a4d6ca6741aaf158e35779fe6c4d8a9a427d/latex2mathml-3.81.0-py3-none-any.whl", hash = "sha256:d317710393fe20579aea39cfe8928fa2ad9b8780896e585326c75e89c1d1d1a4", size = 79185 }, +] + +[[package]] +name = "lxml" +version = "6.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/a9/970b8fa0ecc4fbf1dfaed0d89bbc1fc1421b25ec26a2038c91e872dc6c8e/lxml-6.1.2.tar.gz", hash = "sha256:1055241852f2b02068af4a625a5d32c087db193c12251928af2562ecd2239f18", size = 4210626 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/2d/c292b75049d8b919a515a439646307b971a5f72cd99aaf77d59c9a99e7c4/lxml-6.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da6a4f55f0e3308c07354b1ee239c5550afc212f81629a6067db505ace3b667a", size = 8563059 }, + { url = "https://files.pythonhosted.org/packages/69/55/16395f232cb28182c72a1fb4d9d187163fd05a581a98c37f33e945b77a6d/lxml-6.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f4d2c36fd5997d30ff19c29fb93293401d0daaf87512297d47610e6883964b5", size = 4613599 }, + { url = "https://files.pythonhosted.org/packages/08/20/a65a084596ccd7fd1ed0668b4cf3b68e700da4eac830a0f22ac569f19a73/lxml-6.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1d55a614d2f0457b1f7511c1b7bec0db0dcdd4af4d09d226829eb054c647527c", size = 4935619 }, + { url = "https://files.pythonhosted.org/packages/e1/35/008bf5a5f8809a90a3e62909d8d4458f09b7c034c365b508990bdc38b5b7/lxml-6.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:575fef7f30048b744dffb3e4ff64a18cac7dba3fd26efdea5730ade9d1bdeb33", size = 5078913 }, + { url = "https://files.pythonhosted.org/packages/4f/cf/041b4c15ba3b0421ed828af60993f23cf6e5ea8801efb773b19e248fc6a5/lxml-6.1.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79b428c3242e63bdacf3b526a34e0b8b26583846fc597da84b8f0c3d5ea446b2", size = 5012236 }, + { url = "https://files.pythonhosted.org/packages/06/42/89a2760cd2f2cda28ef5b9591ec775a6a5183d193e7b62ddb936b1565167/lxml-6.1.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12ecfea07d767f6accbf30b014e1c477b5eabb13eb4e8c748215efb52c0e314a", size = 5211283 }, + { url = "https://files.pythonhosted.org/packages/5d/0d/f5607ff466d0d8874d7b778c3ccb64f65ccc0ac430e1961969fd450b899c/lxml-6.1.2-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:bfcbee8ffff4188f4c6d97eceeff36d8eb983cf838933cbc12ce5f5dd51476c6", size = 5343352 }, + { url = "https://files.pythonhosted.org/packages/63/6a/77713b73265d043a513d9e7df2458f07b2a14709f95e3a35a34834785fde/lxml-6.1.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:822d9397033edbe530a13bb1e0091c0e817536b6aba87a9b4ad626ed779ca0bd", size = 4673191 }, + { url = "https://files.pythonhosted.org/packages/a7/c7/e4179e0b9f71859bf9a56b3da91db4c7e85c47072018e7b63e019ff65c9f/lxml-6.1.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4303f904fb6c41b58dc70743b1d8a470aba6c9897427c48324cff1a95673ddb4", size = 5281079 }, + { url = "https://files.pythonhosted.org/packages/22/f4/358200b95081db4fd02c4d81938a07080ae7636f9149befda1c0e5189c40/lxml-6.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdd35422de747237f451e821766e2b6be3dd2c31955c1ecd7f17984c5b9bb62d", size = 5055515 }, + { url = "https://files.pythonhosted.org/packages/fe/06/8fe708d90022bd13122c359d38f3f751e4fa71b871eace7fa81212dadfa5/lxml-6.1.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b3ca02ef3b5920b88119c82eb6badfb2d082b1f681d528a856dcce17c8706da8", size = 4722745 }, + { url = "https://files.pythonhosted.org/packages/ad/1d/9d374182c2ee79a5097d4950bfca9e28011eeacdf614db022b9905266b5c/lxml-6.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4bf14db2f0214003ec7f46c4300e2065668fc93e20448c1c95bac2e952072168", size = 5268962 }, + { url = "https://files.pythonhosted.org/packages/72/89/d0835e464b84d92c43d838bbeaef02f9ac374ab2bb6972411e4c3e80975d/lxml-6.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2afd1688e372d8eafaa6f56c589399e0a87d086a0c110f6346b0b50f42e67e25", size = 5235564 }, + { url = "https://files.pythonhosted.org/packages/bf/ea/0b8acc86d702b9fa1a0194fc7e653087912d340cb10507f4a5bc369d04b3/lxml-6.1.2-cp311-cp311-win32.whl", hash = "sha256:aea814342f6afd20d832937ff8b333cd6506428a39c0c4c70c2380aab1887bfb", size = 3600342 }, + { url = "https://files.pythonhosted.org/packages/65/5c/04480497142794bfb2d98c01ea9972e9b3d0f6b1f017073cabb74ab0b8c1/lxml-6.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:b3db5497af55f7a557c95265dd3b91c75dc56364a7b59f258c45fa5576dce058", size = 4032771 }, + { url = "https://files.pythonhosted.org/packages/f9/88/4c5ca0f808a80b7eaad073269f1fc53992c5c7c905df13d3953d886834b1/lxml-6.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:e8dc3d29f2ed2bbf24c205a86326d6681230ace55abfb3f9d5230f42078ad63d", size = 3674380 }, + { url = "https://files.pythonhosted.org/packages/ee/a4/55eb54507073089ab27743c5da2113c84f0d0b1715b33175fdd943c9652d/lxml-6.1.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d506bdba580ecb1a6ad2e2b5c49445e66d3e1f95894885739094393a1aad237", size = 8602111 }, + { url = "https://files.pythonhosted.org/packages/bc/bf/6332f45d78da385bb01d5cac3fe4acda19f025d1307cbc7ad538355fecbb/lxml-6.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12acd337d2821cb8b9247dfe4b7aa2f2769a3df5ae8511b7e550df42b8f4d3c3", size = 4638376 }, + { url = "https://files.pythonhosted.org/packages/68/e0/21fba0fe74d417fbe976903ae6bc77e92cdce01aae7b636abd87756f4588/lxml-6.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5078ff51e6316c0f75ea8127c2cd24374747fb351f62fb93d1761f8ae5a04a40", size = 4939689 }, + { url = "https://files.pythonhosted.org/packages/de/e5/ce3e885264fdd0bdcb6b49c1ea1842f94281b39e4ff956099e8d57532c60/lxml-6.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9477e14217c212e6023c994a71a1a349db19b0e10fd5bf189666b281ae63b1fd", size = 5105185 }, + { url = "https://files.pythonhosted.org/packages/e6/b6/990a8446c488c70fa25681e150de94b7bf2eaaf387e374d195ab3c8faafb/lxml-6.1.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:261d98065326676d7253882db0198d0aa06748d7ee0443367acf10b148273f99", size = 5011863 }, + { url = "https://files.pythonhosted.org/packages/bb/6a/f70f41363dae27e3bfd6224b128f5ba150874bd32ca4938552930ffa33b0/lxml-6.1.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0666943ee1576fa890a6dc6316ef42e8241b5dd56f67bc5475acb2ac298c6ca9", size = 5638234 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/a65b64f34d556925faef2c4f14167d58c571bc15a3e1f2bba71138830562/lxml-6.1.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04cf9e3f4ee9cab9d9ba05401bef8668840fa9620fcd4d8e85a2d2fd0b0fa960", size = 5244532 }, + { url = "https://files.pythonhosted.org/packages/c6/a9/471552e015e954fc9d960aa27c3d67ebf489683d03f033399a790417c67c/lxml-6.1.2-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:9429d2371d406344ed1da5b5686d9412e74137c07b0171278368ff704f470ed5", size = 5358194 }, + { url = "https://files.pythonhosted.org/packages/d7/0f/bc6248fbec2cc416f102b1267f1567e07510f6fa909bbe8cd2a22d6fb78e/lxml-6.1.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:eff128ffdc093cc6317955934ad9751105d37ed8dbca3ff4ccd751af6be37185", size = 4704432 }, + { url = "https://files.pythonhosted.org/packages/a9/3f/cec859f50e63f1fa338fab43d2362d7543e1237f2475960d8ab0769de0eb/lxml-6.1.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ba58574d710b82ead7cbedea01cac3e110bc3ef82d4731519b74a2c11f7cf5e9", size = 5255038 }, + { url = "https://files.pythonhosted.org/packages/7c/d9/2ced0cf2967115f92a1b8b3ae6bd18763abc3ebef88c98cf25145fda396c/lxml-6.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52f6d4dff133c9778a24e9a2cfc1608930b15869866171aacc5131b5a418a003", size = 5054481 }, + { url = "https://files.pythonhosted.org/packages/d5/f5/4f07386d3c88673daeec3b8cc09a2a4d39fa01c1fc49009791b0746d97fa/lxml-6.1.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8807998c1023d1e9d60e02500f90e85a0752dbc0b670989806bba87b82dd5b42", size = 4785535 }, + { url = "https://files.pythonhosted.org/packages/9a/5a/f4fe3ecbc189f48fba2547c5db5c940a10151d3e86b856a60a533a77e816/lxml-6.1.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2170d0a280c877b6e2dc6738217db947be35dd8cf09ca458b355aa1bab2a9e70", size = 5655337 }, + { url = "https://files.pythonhosted.org/packages/92/c4/f586aa1bf27bfbace2dfdbb704da5c52f0bdece8ee440c8fb4946c940b2e/lxml-6.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c67f3c1278f942e97d8665c2a690324aaea5137de16f056583a21f0ac706177f", size = 5245778 }, + { url = "https://files.pythonhosted.org/packages/18/a1/677494bbaef4d6db5e4633af817414f478865850b55c03ae4bf70fa7b8ca/lxml-6.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:093fbf547d0f3ca02705381f795a050fbb58988be4aac7f79f99f280c4082313", size = 5267274 }, + { url = "https://files.pythonhosted.org/packages/5a/71/b71425b8764d4cb7c92eb970483be7d5610dce2a6316242b5aaae7d260be/lxml-6.1.2-cp312-cp312-win32.whl", hash = "sha256:be365ce8d2d411cf2fb573747684b4fd470fa6224e0094d9d5a21155acc369d3", size = 3602563 }, + { url = "https://files.pythonhosted.org/packages/1b/fb/909584e16d2148c1a252cc2c32dd99fe0e2682459c586d3d7a192e74a0ae/lxml-6.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:b97153ca609b434b712ddfb92cd6af101a7045a7724c542258bd4727a344472f", size = 4005965 }, + { url = "https://files.pythonhosted.org/packages/5f/8d/41207c9212caad0b52749e34739fb9bfab67486729f52a8fe9bd9266fee6/lxml-6.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:7feb72424f19a893ae4f3373c7aae821b1aacb6076b708915c651f0683a97c49", size = 3666641 }, + { url = "https://files.pythonhosted.org/packages/61/2a/e9651f47a31a60b5cae031abc23391ed9aa30c8fc07571d1a38f58d6d770/lxml-6.1.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:351318f5c0eb7fcab5b4fdb507c6f88fb2c4b5e67784c7e5911448c91fffb5d4", size = 8590165 }, + { url = "https://files.pythonhosted.org/packages/61/87/a8098abaf35118767d1703b84c98940a5d833064e0eca39a00ecfe9840ab/lxml-6.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0edde95e4b4278dcc0175eda06dc8aa2631ad9f83ae5dbdbc4f0925e200b0b0", size = 4632474 }, + { url = "https://files.pythonhosted.org/packages/93/cc/fe74d1def7f4fb967c4a825608a074d4dbdbb871b0d6bd59c6ed07d67868/lxml-6.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8326e24ae6c3a6bfb03fa8b4793f9a5d804c125228aa067f652b0428e31b87c", size = 4936196 }, + { url = "https://files.pythonhosted.org/packages/b7/ad/b96e6ca926e26726a99aa643602aac7411ecc1731ddb1b25af8cc57edfcd/lxml-6.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7c534ed898413f439b048130011e99a4245ee13d62d431f6b4f7f2484d02a93a", size = 5093290 }, + { url = "https://files.pythonhosted.org/packages/d2/84/616f5d3b7cd086fcfba3e5add6fccda67f976c1c753ae9ed7bbd317cb9be/lxml-6.1.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e37fe49fe2d5aa40a2cb1cc8176673ad7de0d124e6f4a509d9318f5979c7871", size = 4998767 }, + { url = "https://files.pythonhosted.org/packages/80/88/d5b453a8d083483c9442ad7f5ac5c560796022eb5c80d60b65d75e449236/lxml-6.1.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9b52ea73a37fc64aa3357ff8607801d46dd170506d3cf8253a91a1d91639d4f9", size = 5626717 }, + { url = "https://files.pythonhosted.org/packages/71/45/31e5aa4d4bae024908ba1d03480c7425cf027a28b7e5c88d1b7202bd80cc/lxml-6.1.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8b9a92652e75e7731309ea51db5dee892eef414ce70a6ec3441e5d36bf5189f", size = 5232330 }, + { url = "https://files.pythonhosted.org/packages/f5/5a/2627912420df8b2d31ba3014da5539f15ec85add01d42048864ffefda516/lxml-6.1.2-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:9088da25ecd609965f838d89fda0465a905b48f4dd90331db9845518f2177372", size = 5347054 }, + { url = "https://files.pythonhosted.org/packages/16/86/54ac0f529b22a8f12313726dd49e12961bb46471d9028cc28d2a29408f0b/lxml-6.1.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:0349321a0537d4fdbebb2af06dd1b64676132c72e2ae250de8cdb58f8c43019c", size = 4707275 }, + { url = "https://files.pythonhosted.org/packages/3a/42/ffcdc6e4519be90df907cdae7e88409efb25d823ae4de8846f737dae1884/lxml-6.1.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b20440e578d269c5e8a722ab602ddd0f0cedb8b080006b3f936da9991a593d3b", size = 5240071 }, + { url = "https://files.pythonhosted.org/packages/68/49/5b1d7ab35f013f1127ec48f3108319f58b65b00d5cb26f215adbe86eadfb/lxml-6.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7766e525282dd38fd89567311323e441996eb958e8e816d16b38f782e3aecd2a", size = 5050356 }, + { url = "https://files.pythonhosted.org/packages/b0/57/1cf049d054189b55c8fe8012269234f6602256949b69cd3ba80608a88219/lxml-6.1.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9221442682c27417f10fe11184ea4cce174b25ab52465570b1f3ee3f85f320fa", size = 4780394 }, + { url = "https://files.pythonhosted.org/packages/d5/ad/064488a8fa60e639fd773e421a18bf17541d02a95fbf36238ad7c65f69d4/lxml-6.1.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75530642d8471327e691ab9b0513a5f9c77f38871014ceda40f51bb51765c0a1", size = 5645854 }, + { url = "https://files.pythonhosted.org/packages/85/bb/120e56f3cf1c149bb3b014278fb86d0a6dd552403981081f0ee0a0a57be7/lxml-6.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:678e35f1cbca98f55107511ee21a60568535c950f3c2371819bd64504c980d20", size = 5231132 }, + { url = "https://files.pythonhosted.org/packages/b1/2c/7d49aab893c128671a3276580074cce4c002896145b8dd2893da79633bca/lxml-6.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c2bae42b3a09f977330a08f4a8fe72aec58c4bdb89069d3fe7272a71d885881", size = 5256076 }, + { url = "https://files.pythonhosted.org/packages/72/28/ddea3aa1fa9acfd384fe34d4a2a93eecc07541dd2d922fa9b140c60d8014/lxml-6.1.2-cp313-cp313-win32.whl", hash = "sha256:5848f3de6a8de8a93cff9f068134393ff5fa69ac2a04399f7d49cd67c61c348c", size = 3602177 }, + { url = "https://files.pythonhosted.org/packages/1b/7a/96bac167538748cae2544335855f812fa33e49a9a67bc8b8520dcbd592bd/lxml-6.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:6cb0c87421946030b92b558be416852780a912454e3dcba0998e4497c9c588d5", size = 4004117 }, + { url = "https://files.pythonhosted.org/packages/0a/24/9498fa3c84135956e5ef55ea4d8bd11e999e381f7f210fb6f8c6a980ef03/lxml-6.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:648861c19b775b89ebefa14586f85090b10163367476d77f242c4131c835ce73", size = 3665412 }, + { url = "https://files.pythonhosted.org/packages/27/b5/728b0578791b397ace8d1b101c8b3fe10f36043542f7bb85f82d8bdc3f50/lxml-6.1.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d50a44113fe6800dcc8a859332b823a4735b1e6ae1b0063882e4cca569ec3e29", size = 8609651 }, + { url = "https://files.pythonhosted.org/packages/3c/6b/49209fa6225c15c48a30061f03d3aba75e3c19634813b88bf83b88c525ed/lxml-6.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fa813b0247d0543a563b993ac3dba6168eef59e3a61448432cf5453300c2412b", size = 4639588 }, + { url = "https://files.pythonhosted.org/packages/20/86/80bae4e8bc2eed9d6f017701a3d86fdea56936218efa738911d0b76aa7f4/lxml-6.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d858e718b94033ab4b67e4a58fe3114c65bae01ae2314a62fb39ae8897ed4324", size = 4964846 }, + { url = "https://files.pythonhosted.org/packages/70/ce/4782caee7a22959c1ac67cb46495e03912c22a4ba7d20c163496a519e815/lxml-6.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e3b666f57a5d81562f38c766c762416b0f6eb58a00590546911514b48412abd", size = 5099288 }, + { url = "https://files.pythonhosted.org/packages/32/21/f120967cc43b54e05512dff0c39726b832c836195d30f41f88733ef36ac8/lxml-6.1.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26ff164c6629e5c4d11c9e55d5ea3d6eed0be2a420eee1f55cbce6e2c23e231a", size = 5036837 }, + { url = "https://files.pythonhosted.org/packages/61/ba/8005e9f47598e3ec5c18312c77f94e889580027616678848405c6aeba5de/lxml-6.1.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:962c12b51d0b164f12569af225dea57568477e24a845b96eaccbef6c07e4cc03", size = 5658569 }, + { url = "https://files.pythonhosted.org/packages/6a/ba/add33b3c7ce51462cf7a4637bcfec2eaa258364d6015b989dd7d1216e6a6/lxml-6.1.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47e367dfe341521426692819803e260d0673899c0ff611f14af978d725e2c999", size = 5246003 }, + { url = "https://files.pythonhosted.org/packages/05/b3/a43012748fb861c914c5eac1c1a3bad44282e767499cd02280d4d1edf092/lxml-6.1.2-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:92c2b366028ac01e90399e6d17734ce6e4f4aeddd8ba75fbaf80ea11d6c6d645", size = 5354047 }, + { url = "https://files.pythonhosted.org/packages/8b/cb/813021d9a445713b8d758b9e5eae2ed392cd598d9f119d9b053b37c2ab93/lxml-6.1.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:7e81fc065ede5d58dd0bf0912025aee1bd04c52c2affd61fdb93226a97ce2fc6", size = 4704382 }, + { url = "https://files.pythonhosted.org/packages/17/c9/1155299f4577bebf3c280497534a73e4b8ad8cab3b96074731ad10949d4e/lxml-6.1.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:633ac039cb32366dd5935868e041e385875c017b8cd54ea56aeee3fe29ca5935", size = 5258530 }, + { url = "https://files.pythonhosted.org/packages/25/6e/d76e58384b378b877e140e25b9a9835da00035f81ff70cbe943a3749bf27/lxml-6.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f3194777c0d05945ac91d8594be25d2679d1d826e01e1fc90bae568ff3a547b", size = 5089919 }, + { url = "https://files.pythonhosted.org/packages/f3/b7/898013c0f8891481d0624ab3bd5dd8c8ff827232dfee2a5d1f8bf970a7cc/lxml-6.1.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1133bd969f2bfcc6b0c0cf7cdf5f2631e62b23fa2471ee8bd44f6ab73554ee9a", size = 4741972 }, + { url = "https://files.pythonhosted.org/packages/c3/47/efb53c4d7b655831c03317a450d9da439b0829c61f34d9d4fe7c863445d6/lxml-6.1.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1edca8f4a92b94e873093df959f141d388f2141fcad0c47598442fb4730ef57a", size = 5683241 }, + { url = "https://files.pythonhosted.org/packages/da/0a/0ff36a584cbba14a71326ee8a5300694400f0b97927d1f90a87d95b17d4a/lxml-6.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8512b3775d68994dd1d6d533161e0a214f2ad9c634659d34a99c98e86c6c3d68", size = 5245892 }, + { url = "https://files.pythonhosted.org/packages/8f/9e/303717a1aa56d4bd775c91936717d3c9e8d999a8e8b68b00979c4c1f93d0/lxml-6.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5005c0c9e4d749a76a2ff8bd5918a8bb248df8e08e73a55654b9f79c9cd1e2b", size = 5269528 }, + { url = "https://files.pythonhosted.org/packages/ad/c2/2ae7cb97089eb86bf0689516db3cf280a007b6145853d2a0235a1f01683d/lxml-6.1.2-cp314-cp314-win32.whl", hash = "sha256:e17e2c30e27f56da5551e7a425888b45f013e940b99ab07d125a1c33f77a4605", size = 3662743 }, + { url = "https://files.pythonhosted.org/packages/77/13/a3d483230a09201e211ceb1aa208b1374d27d23b8b180d74dba14b30f6b3/lxml-6.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:87e9673cd8a3445024fe38e7f91b55fa3428437eec9b7a7ff7d81979520c0d2d", size = 4073942 }, + { url = "https://files.pythonhosted.org/packages/1c/f1/c1445d4b6ad7c51e39d4e2ebbf015a4880f5b297a4ab0e77e4d0e5b70110/lxml-6.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:878e7c8ada8f92c52f13f35a2ab98ef0adf7fd0211d164fc2af589e4c3cfed63", size = 3749235 }, + { url = "https://files.pythonhosted.org/packages/9d/eb/598c76f4ce19a67c635e86a46d880cc854f308f39a6f1fdf13bbb01813ec/lxml-6.1.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:94162456ed0a64fb1c06915df5bd06af4675ae3966d6048fcb73b0906e0e0222", size = 8860315 }, + { url = "https://files.pythonhosted.org/packages/da/c7/1f9fac7b566a86ad0da13dcc0259164266469c0ad86744c740ccd5c2a081/lxml-6.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4b0fa7109b1d0bc1747d8241a0853e135eefb1c978685241b544c46937383efd", size = 4755176 }, + { url = "https://files.pythonhosted.org/packages/3e/1b/cfda9307388d496e7eeb7493d9455896b8137ed95f51f3d6ae6ddcc14a47/lxml-6.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:604f4778632588d7c000e7e19430639dc12fca58b5b6e99edffba7631725ef0e", size = 4979444 }, + { url = "https://files.pythonhosted.org/packages/e5/71/f732c8919c45b7f29acf443288c6e90036877a67bfeeb1acceb0fffa011b/lxml-6.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a096d6a5f96b776a5b020cb45c17c545effd2a3b6639e6fa97bc95537600923", size = 5115887 }, + { url = "https://files.pythonhosted.org/packages/30/00/121d52b944f41e33ea86c62875f902d24982842dc7231ab154ac5a6c6593/lxml-6.1.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6454d184d556eaf4cb3d6f69e405d21602d6fdcf08b8d57796824275986c6595", size = 5032418 }, + { url = "https://files.pythonhosted.org/packages/70/19/cadb73c7fe48c7563dc8ab62ea53d5b920c8911bfb808507a6daa82e78d2/lxml-6.1.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b68f2548259bb04e0b3d5df0c397abe8b0080f5e1ffe4019fb7a8bf01a9339e", size = 5603304 }, + { url = "https://files.pythonhosted.org/packages/13/32/9de126a14d5a5db8c371c5ec869178417db226707b62a47273a95ae6df7f/lxml-6.1.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c9cc4b6532abe154dbdebb42aaba8d52c852919591e45067f5b7d46a0405e88", size = 5228938 }, + { url = "https://files.pythonhosted.org/packages/ff/9b/22dd9e843629ed04652591fb220eb2bf2394d97be3be377d60d8083405d7/lxml-6.1.2-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:57188e441ab24f906bd5a5c14eb55363ab51aa6c0de549f3dd320043721cc118", size = 5317790 }, + { url = "https://files.pythonhosted.org/packages/2c/2c/b12a1dc121f81c280635c721c7bcaa341441fcbe37397f60b8915048aece/lxml-6.1.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d0bfd719c254bbe60ea022cff0e6ffb799a6fa7d4d72852cebe0257957b32d68", size = 4646468 }, + { url = "https://files.pythonhosted.org/packages/57/41/fd87a41edc531e7969c25ab1d6b52b5b041eb108b88f6394d6afb4374396/lxml-6.1.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:be6f87cd224254a8f81324e34cc655508b83f1d70458a1a39857ad2aa9925852", size = 5240607 }, + { url = "https://files.pythonhosted.org/packages/6e/30/713ba813b6e6673c6dc34733746516017efcd17949b767b154cc50bccf20/lxml-6.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:074a88f70a7360a4a0c5be5d898062cd26f898c25b459efb1bdd43ae700c5a1a", size = 5086495 }, + { url = "https://files.pythonhosted.org/packages/33/f8/6532ce0fecd9c326d06b08274ee075cc28dbc9f5e9285355db8504689114/lxml-6.1.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:9031f5f01452681abf39fdd65f84a70cb01a7572a1bbf570042e826b1232d07b", size = 4758801 }, + { url = "https://files.pythonhosted.org/packages/74/b6/5a1f7833ebaa0dd33c28f6f9755ec6ff3891bf63f097634b44e6da1bb65e/lxml-6.1.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:cfeac14425fc7a6fca7864b774d4ee63547926158f4a18c67d77b2c9a948acf1", size = 5626977 }, + { url = "https://files.pythonhosted.org/packages/e6/20/6ae0fc1b45e20877cdcfb1168ceeaf9abb0fba5ed36bd639a260e7b2101e/lxml-6.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8ec111ff8067325f85c08aa9c2b26179ec0537bb89c003fde31127139f85f82d", size = 5235036 }, + { url = "https://files.pythonhosted.org/packages/47/b4/2bc7b37fbb990ccfb7d30393660741592177224a94e07d842c8da70638e8/lxml-6.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:48e912f37c99a297175ba955f55a47c0e1c834b506ef162e52a6e4fe276e6e45", size = 5252270 }, + { url = "https://files.pythonhosted.org/packages/4d/0b/07fb8e1dee29a78e2c5fa5c6c914218be76a6406baff27907429566e90ec/lxml-6.1.2-cp314-cp314t-win32.whl", hash = "sha256:7c444c3a6e8e75334879980eed96568f0e12064c8b1913424eac1805e976736b", size = 3902666 }, + { url = "https://files.pythonhosted.org/packages/58/ab/3371527bd9820aae6f511697c93032ed197b0d8dab0f17818f18d3099637/lxml-6.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7f35ba7667004ecdafebbe08da7c9fa06ee6195275bb7ef7a29ee1901e69519c", size = 4401011 }, + { url = "https://files.pythonhosted.org/packages/e6/bb/e6de9b2546a4e6df4fb52fb18921906a8b7a041aba06570995759a4d6d8b/lxml-6.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d117f39b28ab8a330a74abdbe61c2255b51973b238db25fd6c2448de1eb2a02d", size = 3823384 }, + { url = "https://files.pythonhosted.org/packages/0e/83/7ff98683e14a148191278728d11ba782c3d5137886d49fd95ab4036efa1b/lxml-6.1.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:1e3c67b817867c484794d7fe0d73045d7d0c67460c78a0a1249a9e92266e6a0e", size = 8609183 }, + { url = "https://files.pythonhosted.org/packages/24/39/c39f05e8240e98009dd3d4ceb248319d0f36467babc5f90a909ed0c5b68a/lxml-6.1.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:d3e97ac4353cca3fbbfa829bc0c6a913771573d1c6d46932d4335c46f2b7796a", size = 4639898 }, + { url = "https://files.pythonhosted.org/packages/c5/bf/25e26b089510940a0777ab334357874569255e50930224c8159cd649e754/lxml-6.1.2-cp315-cp315-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:827438bf6c8292d22a409bb7990d7cffce410f33e7664e46ca74d2ecc26975ef", size = 5037527 }, + { url = "https://files.pythonhosted.org/packages/65/6d/aed3a58a3d662f7367a537fabe8c549f1446dbd043719e0ae8cd53f47819/lxml-6.1.2-cp315-cp315-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c470d192e27f97842a068cf12a1c1296b20ca716c56a9249715c6654bc192d19", size = 5661918 }, + { url = "https://files.pythonhosted.org/packages/a5/ca/706d32b6957c0c2e005a9833e8fc528449196b38d5cfcf9e0fd86a96fb00/lxml-6.1.2-cp315-cp315-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef0b8ba6e13597f681b2b4924ca9c4e8c88420bf0e21d9a9006c757f2fc39d1f", size = 5249359 }, + { url = "https://files.pythonhosted.org/packages/ac/e9/445ff43f56fcffa06f6f3a7189920c216f3eacef68ef834d4111cdbd86ba/lxml-6.1.2-cp315-cp315-manylinux_2_31_armv7l.whl", hash = "sha256:65c32ddc5d0750129c7b119fb57d48192b76d334c21e6b690d19dfb06b34af79", size = 4704548 }, + { url = "https://files.pythonhosted.org/packages/69/78/20b8b7e79a1b1d9cd4465c332d62962858562b446692f16a27068fa54b85/lxml-6.1.2-cp315-cp315-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0aa07065497f191ad26c4b587ce5dbb5a7105285a3789aafd0661750e8bac537", size = 5261170 }, + { url = "https://files.pythonhosted.org/packages/54/ca/84a0e1148bf511e12e0d99732a4e136a3bf1b91622f0a1b197796e2ff984/lxml-6.1.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cde6b8db7d2e5135129eb5e74b7b44dd2053aa767cd5023541fccedddc262453", size = 5090576 }, + { url = "https://files.pythonhosted.org/packages/6f/f8/1ef6fc7070bed8753315f2e4ea66bc0d37620e1444d014db7f0267b8faaf/lxml-6.1.2-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:b28842b30c4bc2e6afe137d98a5d2071a62589471e76d053bea55b0e53298af9", size = 4744614 }, + { url = "https://files.pythonhosted.org/packages/87/f6/3a4824cd1c1b81d996d2d75bbd176ba13fbe9b5d89489290d93ff9558486/lxml-6.1.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:11f529062255209a421ae4de5b1bb36b2f0a2e1a700745e675a4bf4084d13c00", size = 5685792 }, + { url = "https://files.pythonhosted.org/packages/64/9a/f133bf16a67149e00ca5d8a8f1ae662c30a86c303aa242693b67f8e19856/lxml-6.1.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:f8b89b3be75a37509602b03f9cfa1a28298d4eed4625748148307aeb907901b7", size = 5248972 }, + { url = "https://files.pythonhosted.org/packages/50/63/273e7e8a73a5d183d8552dfdaa131dfda0292ddab7bcddc5a66a0ae525d8/lxml-6.1.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1a2331da06dd55a8184985306eb2afd72d708283ce7e85d67bba77317b785060", size = 5271809 }, + { url = "https://files.pythonhosted.org/packages/49/eb/614117c36a28909e79ff7cdec87008f0bd996478f35cf72309189cf398b1/lxml-6.1.2-cp315-cp315-win32.whl", hash = "sha256:442766b326d9892585a64e8c6c4b5ab81d0e6c0538c9f0fc11a84dc101a5d97f", size = 3662854 }, + { url = "https://files.pythonhosted.org/packages/5c/e9/06aee6107cf8e7b870f10f82539f366cba10dc6053144cca80e838caf8c8/lxml-6.1.2-cp315-cp315-win_amd64.whl", hash = "sha256:a7fd1dd6faa3df9dcd8f1765237362cd885ca62cdf77a7c5f5ea383ae5b6048b", size = 4074590 }, + { url = "https://files.pythonhosted.org/packages/84/bf/dad9b6baf9b26d79584834e15cef2a5dd0a13c7b1df08831e8f18244b494/lxml-6.1.2-cp315-cp315-win_arm64.whl", hash = "sha256:054175250531a5fb102d485743ff16412279c93add12385b3b1c3d7b16d8deaa", size = 3749336 }, + { url = "https://files.pythonhosted.org/packages/5f/9d/cd0c43d45e2eb52df7735c6558f24054ca633499191899b0cb9040fbbc3c/lxml-6.1.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:84a2a46b93b789d8acb44cfcb3d967ce9dbe29884ddb93fbb1a33f0e0c8fcd86", size = 8857688 }, + { url = "https://files.pythonhosted.org/packages/0b/26/27093dc1a9edbdd8a54652f237a387f7e63ec0192efe708bc2576d8a1383/lxml-6.1.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:4aced3284e0353c798b060fe2c175eb81410e99b9a7e2ae6951be5333732b111", size = 4754422 }, + { url = "https://files.pythonhosted.org/packages/2c/ee/502f7c93507f57eb496744a64da8f4ca86855cf88e48d14584342f1bfd92/lxml-6.1.2-cp315-cp315t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47c92dc5167de16e27ace8332454f12ba172dcab04f7a78a9eae14e2e41b6a41", size = 5033396 }, + { url = "https://files.pythonhosted.org/packages/bf/72/c4cbbe72f951650f2afe43a70e51687e111d82b9bec46e3310ea76419d46/lxml-6.1.2-cp315-cp315t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:40366c23a938008a3bedfcfd80709b3a857c188b4d710b083e978ef5d2c1c715", size = 5615298 }, + { url = "https://files.pythonhosted.org/packages/14/83/a3df966d6d7b6513e9dfb6fbfb041c0619642170359c1b36ab20a83e59eb/lxml-6.1.2-cp315-cp315t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c4c6dc1b2485aaa4adfb6ed754f90dddcb2b96a66bbebc9e1ac242b5ce5e818", size = 5236282 }, + { url = "https://files.pythonhosted.org/packages/4f/85/8692ec8173c9f8d295735b9bf410d202317e7b3ed11141e80a30f421f409/lxml-6.1.2-cp315-cp315t-manylinux_2_31_armv7l.whl", hash = "sha256:3a698fad6f122a9b3e2dc2fb598c1de7329c74a67c7a334c9109a440de2508e5", size = 4650647 }, + { url = "https://files.pythonhosted.org/packages/11/e7/dbe3cece28a5bf82997a091d9dbb0fc49e725a5fa86550897ee2cf6412e6/lxml-6.1.2-cp315-cp315t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:14879fa5eb2b793c040bbfcb62011aa3015c65d6c9875e063ea98ce2029d51fb", size = 5243387 }, + { url = "https://files.pythonhosted.org/packages/99/a9/81a2d27640db0d27200b2f32339a54e74c36d58feb5ad528b87d52a59ecc/lxml-6.1.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b631174cd2e4d9f8a94ef17f911c6ded10ede93b5e7860dee7bbf85961d321e9", size = 5092624 }, + { url = "https://files.pythonhosted.org/packages/cb/f4/0b0304c70c087f618d95b0306738b070bd556afd09c2c92589b78dbe5eb0/lxml-6.1.2-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:ceafa5e0536c62a5cd9f65327fa0b57d6f0b0e3435daf2c98a78d0dde7ecbae1", size = 4758742 }, + { url = "https://files.pythonhosted.org/packages/a2/ec/f9fc45f1d01b632b673e11880e75292dff9953db9f426d1a38201b8eb5f5/lxml-6.1.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:7c482e87cc86bed78a50462560675bc2c348ef72c47596f9b933346d5a8e920e", size = 5649540 }, + { url = "https://files.pythonhosted.org/packages/6a/0b/d65e0458c2bcce0df68d5cc29ad0006e76446f02d9e50caf188fd1fb8bae/lxml-6.1.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b1c0d2dde8a50520efc51644587f0fc4810e3af7d3e029d7af0be93bf39e2b5c", size = 5234869 }, + { url = "https://files.pythonhosted.org/packages/ee/62/1fee828238badd3bfe9544f5cc9ce6ded421ef38e9634030445dedd78b36/lxml-6.1.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:dd7ea3fa47154b9fff90591b961e41b3718bd7fcd5bc2d9bb47e9845c8ace088", size = 5259992 }, + { url = "https://files.pythonhosted.org/packages/20/18/35fb14dd6baccbffa6daeb2369802f04a94e3f73db3c7bb405dbab009729/lxml-6.1.2-cp315-cp315t-win32.whl", hash = "sha256:87534cec6ea325435e4adf2326b0cf3110eee9a47abf73652eb155db639c08c6", size = 3901151 }, + { url = "https://files.pythonhosted.org/packages/f0/b6/07530896ca062bc3d2f09d5cb8a48e799c05b12c496205db03159ba13b6c/lxml-6.1.2-cp315-cp315t-win_amd64.whl", hash = "sha256:4e220a9c297e5d36895d489a08c9a3f1f6193b6414e702c5fb751e4a3767f8d0", size = 4395355 }, + { url = "https://files.pythonhosted.org/packages/31/a8/237d8de1d77085cfd41d0c6049a044d8d01886f3afb7f1eda2f43d900a96/lxml-6.1.2-cp315-cp315t-win_arm64.whl", hash = "sha256:f16a407766bac51c65d605b06d900821751a79aa20e12185f273f14a17180e7b", size = 3822823 }, + { url = "https://files.pythonhosted.org/packages/b9/c9/11bfea1b3afc7a27ce74222b2e12b97005f3b81aa0011313769a14afd60a/lxml-6.1.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4622c5616683faf63791b349e6c8dad7717412dc5f29f4febe7575f110609a86", size = 3942892 }, + { url = "https://files.pythonhosted.org/packages/c4/98/9885a4505758885c113af2bc2335a9fced99cb01e07e42895a62f1eb97fb/lxml-6.1.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:733dfb492ec3dfef8350a5cc896e90d202c5171e791e1609e77563751d69a15d", size = 4213061 }, + { url = "https://files.pythonhosted.org/packages/2c/5e/e80d9e7d6e54b0693df60c7eeeed4aa19e2e3936dadf0676e6a3e8ac1ee1/lxml-6.1.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4618b20f43dc98b49569b1dc822176140ea0f2598d672a6989187ba49bcbfec1", size = 4322013 }, + { url = "https://files.pythonhosted.org/packages/52/22/2e896cfba4e86b805eb8a3259cbdc1601971dc8fda5b1db2044ec2a3e6f0/lxml-6.1.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f93bc5e25992f5545709000d840c6cafdbd022781a7a0ed79d58a5633733a4e8", size = 4257333 }, + { url = "https://files.pythonhosted.org/packages/b0/1d/9dbdbfa284ea96aee7c368e0ac73994f7e1375281070c355bcd85d4f7a77/lxml-6.1.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:662432a6103e671d971e06e75ed146d9ff67f39d2c98c2f26613b6057f54eafc", size = 4410828 }, + { url = "https://files.pythonhosted.org/packages/f9/f2/fea24b044219458c252e0a0a08074a27dc9e28edb85f83533e36e3ddb57d/lxml-6.1.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ba0dfead73be5be9ad0b7fbf9f31ff29c1b1eae858816dfc8d85099d6e4af0d6", size = 3511278 }, +] + +[[package]] +name = "mail-parser" +version = "4.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/b6fc717f7a7d9188e451e9d30e98b3f61866313958f866f9da7ff5ed3c84/mail_parser-4.6.4.tar.gz", hash = "sha256:1929109b7934ee061c1533a897b18c57fd0adb3548b79b5e28d901819bade872", size = 2898988 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/f9/b35a02c12346111e997ebfaa46ebeb759828373a3eb9f0e82c1e826b3530/mail_parser-4.6.4-py3-none-any.whl", hash = "sha256:bc6e437b3afe38091893e7b6ea49c7f2188ad7616abdd4fa3059e85f1b69efce", size = 50073 }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687 }, +] + +[[package]] +name = "marko" +version = "2.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/6d/671a18bb386adca6311bca6c2fe6bec873947ee501cc5f8f509ec06bdde0/marko-2.2.4.tar.gz", hash = "sha256:c042c66f835425673123d7536b39b4660de3b68e30078c70fd26245b31170683", size = 151013 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/78/751d49c8bdfa0b30147c99235516433af6b63eca367ff28593c7346a0494/marko-2.2.4-py3-none-any.whl", hash = "sha256:d80510506edba096ec49d4720a09645fa0bb78e7b7b88697f20032fc19730aa9", size = 46753 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631 }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058 }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287 }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940 }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887 }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692 }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471 }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923 }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572 }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077 }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876 }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020 }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332 }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947 }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962 }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760 }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529 }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015 }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622 }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374 }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980 }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990 }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784 }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588 }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041 }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658 }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066 }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639 }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569 }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284 }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801 }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769 }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642 }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619 }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408 }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005 }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048 }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821 }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606 }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043 }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747 }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341 }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073 }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661 }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069 }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670 }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598 }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261 }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835 }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733 }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672 }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819 }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426 }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "mpire" +version = "2.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pywin32", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" }, + { name = "tqdm", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/93/80ac75c20ce54c785648b4ed363c88f148bf22637e10c9863db4fbe73e74/mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97", size = 271270 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/14/1db1729ad6db4999c3a16c47937d601fcb909aaa4224f5eca5a2f145a605/mpire-2.10.2-py3-none-any.whl", hash = "sha256:d627707f7a8d02aa4c7f7d59de399dec5290945ddf7fbd36cbb1d6ebb37a51fb", size = 272756 }, +] + +[package.optional-dependencies] +dill = [ + { name = "multiprocess", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/aa/714635c727dbfc251139226fa4eaf1b07f00dc12d9cd2eb25f931adaf873/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1bbf1b69af1cf64cd05f65337d9215b88079ec819cd0ea7bac4dab84e162efe7", size = 144743 }, + { url = "https://files.pythonhosted.org/packages/0f/e1/155f6abf5e6b5d9cef29b6d0167c180846157a4aca9b9bee1a217f67c959/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5be9ec7f0c1c49a4f4a6fd20d5dda4aeabc2d39a50f4ad53720f1cd02b3a7c2e", size = 144738 }, + { url = "https://files.pythonhosted.org/packages/af/cb/f421c2869d75750a4f32301cc20c4b63fab6376e9a75c8e5e655bdeb3d9b/multiprocess-0.70.19-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1c3dce098845a0db43b32a0b76a228ca059a668071cfeaa0f40c36c0b1585d45", size = 144741 }, + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948 }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457 }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281 }, + { url = "https://files.pythonhosted.org/packages/7f/74/d2c27e03cb84251dfe7249b8e82923643c6d48fa4883b9476b025e7dc7eb/multiprocess-0.70.19-py313-none-any.whl", hash = "sha256:8d5eb4ec5017ba2fab4e34a747c6d2c2b6fecfe9e7236e77988db91580ada952", size = 156414 }, + { url = "https://files.pythonhosted.org/packages/a0/61/af9115673a5870fd885247e2f1b68c4f1197737da315b520a91c757a861a/multiprocess-0.70.19-py314-none-any.whl", hash = "sha256:e8cc7fbdff15c0613f0a1f1f8744bef961b0a164c0ca29bdff53e9d2d93c5e5f", size = 160318 }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477 }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504 }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194 }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111 }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159 }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936 }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692 }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164 }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877 }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487 }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945 }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406 }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528 }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119 }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246 }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410 }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240 }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012 }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538 }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706 }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541 }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825 }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687 }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482 }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648 }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902 }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992 }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944 }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392 }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220 }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800 }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600 }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134 }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598 }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272 }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197 }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287 }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763 }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070 }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752 }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024 }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398 }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971 }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532 }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881 }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458 }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559 }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716 }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947 }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197 }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245 }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587 }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226 }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196 }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334 }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678 }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672 }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731 }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805 }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496 }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616 }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145 }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813 }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982 }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908 }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867 }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511 }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064 }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157 }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728 }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374 }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286 }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263 }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin') or (platform_system == 'Linux' and sys_platform == 'emscripten')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918 }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758 }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827 }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597 }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200 }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449 }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060 }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632 }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.20.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "(python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin') or (platform_system == 'Linux' and sys_platform == 'emscripten')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296 }, + { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588 }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554 }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489 }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672 }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992 }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106 }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258 }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten')" }, + { name = "nvidia-cusparse", marker = "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760 }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980 }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568 }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937 }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344 }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586 }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.29.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712 }, + { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000 }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.3.33" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/ee/580ca6f29dcab0221db8706badca1bbbb084f1975c4d4e83329c3a7e31f0/nvidia_nvjitlink-13.3.33-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:26a6de7fb4c8fdaa7703d3dad720d6d427ddfea5c48a528fd97c11733ad830e5", size = 40742423 }, + { url = "https://files.pythonhosted.org/packages/69/30/45414e35ff2eee7db3da037e5707037ccf9d2b5218ffbdb055ea4d5aa98a/nvidia_nvjitlink-13.3.33-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce48b37dfeb3cb1eae4cf85adacb47d7a6539ea2272870c9a3628ce275c2037e", size = 39168635 }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947 }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546 }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047 }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878 }, +] + +[[package]] +name = "olefile" +version = "0.47" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565 }, +] + +[[package]] +name = "omegaconf" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pyyaml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/0e/152509871bf30df6fc38569f52a2db9b55dd41aae957adae50a053ac7778/omegaconf-2.3.1-py3-none-any.whl", hash = "sha256:3d701d14e9a8828f1edd28bb70b725908b34277cdd72cf7d6a83f94dadc6b6a0", size = 79502 }, +] + +[[package]] +name = "openai" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "httpx2", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "jiter", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "sniffio", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/d3/50ffb9a7bce5097ffeb476905c0661f4468a3ca7bb489b152542f14fdd8e/openai-3.5.0.tar.gz", hash = "sha256:743738bb458a586d0d02d173bf398d29d7d7a80d182d167aa74f1c08814ecc78", size = 1355486 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/9f/03bba05ade050f306c9b9e8b99e3681791dbb30148cd76a793c277fed494/openai-3.5.0-py3-none-any.whl", hash = "sha256:7c0873d379655f65fa515c5692c8620e73bf6a6dce3e63f37589bc99bda3fdfd", size = 1697968 }, +] + +[[package]] +name = "opencv-python" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4c/a438d23e09ce2033c09f7b784ad2fbdb0adf529e434101ed28f142226f98/opencv_python-5.0.0.93.tar.gz", hash = "sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2", size = 81802749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/75/76f6ade78f6102c61034f828e2a22616708df2c9504bc8d6af9dd8f73dc5/opencv_python-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898", size = 48322443 }, + { url = "https://files.pythonhosted.org/packages/15/8c/bc1bda6aae69a32e9d84fc34153ba104cd25226861eb4aea33b2cea4860d/opencv_python-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b", size = 34782755 }, + { url = "https://files.pythonhosted.org/packages/f4/8a/b04776ec45d2dea08a1b176f1829201db3515d4ed16c35f8fcc9fa7beb16/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac", size = 50614064 }, + { url = "https://files.pythonhosted.org/packages/95/54/eb47866b94f2b5b42dde17644b78055ef1ee05aae59962c7290e55270803/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881", size = 71064711 }, + { url = "https://files.pythonhosted.org/packages/93/da/962579f1e703cbf8c5422fd1f576467dcb3b5b0b0b81c1471c979764353a/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2", size = 49798576 }, + { url = "https://files.pythonhosted.org/packages/cf/4c/c73f828fdbcd37eaf21d08fa852544a3ca7c2dbb3ea76873d64f2ea413d1/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039", size = 73783032 }, + { url = "https://files.pythonhosted.org/packages/e2/4b/edaf83b996ca5a1a3d8ccad485706b9c6d4742b13b9c4586bf1c1e7d9423/opencv_python-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157", size = 35564734 }, + { url = "https://files.pythonhosted.org/packages/21/f0/9fa6e85cb10c8eb36a0222d27e50fe381b86ce49a55446bf39f491727564/opencv_python-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2", size = 44000345 }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956 }, +] + +[[package]] +name = "pandas" +version = "3.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "python_full_version < '3.14' or sys_platform == 'emscripten'" }, + { name = "python-dateutil", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tzdata", marker = "(platform_system != 'Linux' and sys_platform == 'win32') or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ef/f1fd7431d635bf20015489bf0bd69c17fff1018de773540f651455a3916b/pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282", size = 10397178 }, + { url = "https://files.pythonhosted.org/packages/31/b4/0eafac990a431561187694126de01f9b12559549b4d86360c0c4bd870fde/pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298", size = 9990736 }, + { url = "https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899", size = 10814438 }, + { url = "https://files.pythonhosted.org/packages/d1/50/d6cc4d7e508bbccf5d6027314a8312bc7ac73d0ec7f195f53838daafab40/pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a", size = 11323634 }, + { url = "https://files.pythonhosted.org/packages/70/2b/d5f0a8c90dd0ae04e64ba53b871afb796ec026b615086d382ddc2ade729b/pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928", size = 11850860 }, + { url = "https://files.pythonhosted.org/packages/5c/30/183aec2e19adf778a98d29b5729a0a68f4cc4ebf9b9c3b70d0297355bcb1/pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d", size = 12411100 }, + { url = "https://files.pythonhosted.org/packages/fa/9a/31f4983f191af51ab2a8f2d0c7b33dff3a84da26533f982fff02c2f9e28b/pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a", size = 9968804 }, + { url = "https://files.pythonhosted.org/packages/49/97/7886c89a39045c69ad82cbceaf3343810480c8ef49a216319ce8183860a6/pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a", size = 9205447 }, + { url = "https://files.pythonhosted.org/packages/1c/54/1dc810ea558d1320b597aa140a514f2fdf1d2ea09c38cf556f13ea712ec9/pandas-3.0.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d", size = 10411717 }, + { url = "https://files.pythonhosted.org/packages/68/56/fbe81c09195924d8b7b8d4461a20458fe80a6a5ed6b24f0314da684277e1/pandas-3.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc", size = 9957095 }, + { url = "https://files.pythonhosted.org/packages/e0/51/fac252f4a913ed5eabf3c11b880a9e8d5a6c10f0b2129d0462212d238b4d/pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc", size = 10485458 }, + { url = "https://files.pythonhosted.org/packages/12/98/e976540c1addf70442be7842a18cf70884a964abbf69442504f4d2939989/pandas-3.0.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d373ce03ffd84010ed9839fa73672a9c8256990532e158440c0085db7d914b34", size = 10998091 }, + { url = "https://files.pythonhosted.org/packages/a4/8c/1f29b5be8d3fc47dd7567eb167fabba2085879b31e0287ce7cba6d3d2ff4/pandas-3.0.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a29c53d85ea98c5e792c59ef82ee9fbe6ca902c0d0adb6b23f45ef894cd7bf6", size = 11499501 }, + { url = "https://files.pythonhosted.org/packages/9d/e2/bd9c98ad2df7b38bde002adde4cdf353519da51881634323b126c55997f9/pandas-3.0.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5ad3b02ed6bc7d7ae9b70804b2c6aa31827489d150f8e623ce82491b82085d7", size = 12060559 }, + { url = "https://files.pythonhosted.org/packages/f3/9a/ffbd852d58bd74a617fe2f8ee6a58a96982271ce41cf981eab22190b4a4b/pandas-3.0.5-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:b2acb4650527eec6822c3dadb2b771277b65e7dae7a267d4bccf65fd1bb3fbce", size = 7197652 }, + { url = "https://files.pythonhosted.org/packages/70/b5/d2d3e9ae73362ba4229651b0ee1455cf78073a1ce585f6ff693782ce263e/pandas-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:80a611068e8a3ac23f7398c6c14eb46dc974e5cc9997f653e2dcfd1da74edd41", size = 9831691 }, + { url = "https://files.pythonhosted.org/packages/52/51/dea1e89d6a6796b9c43f85a09b484ee03edb8a4c4842e73e200a8c11301c/pandas-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:25ff585b972a18ef1fe9ffa3ac6544d9950508aa76832e5147640b6022821e49", size = 9105796 }, + { url = "https://files.pythonhosted.org/packages/bf/09/7b95c4a0025227d6f118c4039b423412ac6a982db02864166185d812fbc7/pandas-3.0.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b", size = 10385742 }, + { url = "https://files.pythonhosted.org/packages/8d/0c/dc78fd8c4da477b4b5e8ad37295af352190d21ef63a9ee1bc071753074cc/pandas-3.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3", size = 9932067 }, + { url = "https://files.pythonhosted.org/packages/3e/71/3592c055cf44df9808550f9368ceda80ff2b224d355ef73fe251dcda1802/pandas-3.0.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b", size = 10466756 }, + { url = "https://files.pythonhosted.org/packages/e3/70/4363150359f95b4cb4bcbb34ca23572bb5495749a621a8f3d5a1ddfd293c/pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be", size = 10938525 }, + { url = "https://files.pythonhosted.org/packages/f7/d0/317e7a0c67c0e69fa905a0161409397a7dc2d46ff611f6ca4803352c042b/pandas-3.0.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58", size = 11489303 }, + { url = "https://files.pythonhosted.org/packages/f1/8d/36dade89b49e4f9d5cbdbe863772581f98c0c6d78fc39ad4c557f6f2e17e/pandas-3.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee", size = 11989004 }, + { url = "https://files.pythonhosted.org/packages/9c/ba/18c4ec8a746e177da05a9e7a7963781d8ea195780724f854601b6ebd6b78/pandas-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6", size = 9826896 }, + { url = "https://files.pythonhosted.org/packages/de/ec/28a57266b753799a87b8bc79e7887ac6fd981b8c6d2978a0b7e7b6bd708c/pandas-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e", size = 9094790 }, + { url = "https://files.pythonhosted.org/packages/51/2f/cf6aae281264f4463f0875bcbb15fd2bb6d291cc535187dad1732475e4a9/pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36", size = 10390034 }, + { url = "https://files.pythonhosted.org/packages/06/ec/5189518c7a7659c4bdcc6b1eb32c46c6f3c86b0661ffd84143d1112c7732/pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c", size = 9980065 }, + { url = "https://files.pythonhosted.org/packages/ea/f1/598503ce8d7e3c35601e0747ba288c7864baae66380725bc12f13f884dfe/pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0", size = 10545532 }, + { url = "https://files.pythonhosted.org/packages/fa/de/ceae2adf7034e07e9910299fe412e1819c4f0dd520700a888bcb03625448/pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b", size = 10963120 }, + { url = "https://files.pythonhosted.org/packages/66/25/86e0f4451874eb79e688deeebe3c451fec4557f8952005818d800ee8ac7e/pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94", size = 11563178 }, + { url = "https://files.pythonhosted.org/packages/f3/45/8643daa3b4147e433adfcccefdd0380d3aad79d86b15d8999730fe1944d5/pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da", size = 12028708 }, + { url = "https://files.pythonhosted.org/packages/96/58/ad979ae617615576e8aafd569c9d4b62f1191d896e38f51d66ba06f3b89a/pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92", size = 9951806 }, + { url = "https://files.pythonhosted.org/packages/69/32/7ac03886b304049a9d2625ee88f59af760d8a93bd30ed9239bce7b9869a8/pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85", size = 9238297 }, + { url = "https://files.pythonhosted.org/packages/be/ed/1d1f2ee5547d5167face2376d11c8b2a4c7bfff5a416ee7a9046891fab1e/pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca", size = 10849690 }, + { url = "https://files.pythonhosted.org/packages/57/55/17e17152e98fbb0c4b1e562bc65387a2f20a80db0f4a86bf8d3a0e4248d4/pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da", size = 10509945 }, + { url = "https://files.pythonhosted.org/packages/88/90/817d44dbf83facf9556f33576d9af0a241981e7bb5c00606c0bcb5df8dda/pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a", size = 10392197 }, + { url = "https://files.pythonhosted.org/packages/f1/da/889f00c0a6f5aa1545add70abbf01502dff87ab577adb855bd631c54d2f2/pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040", size = 10862726 }, + { url = "https://files.pythonhosted.org/packages/bc/98/f1e934fb3c98fce859c6147c6785816c7b5b9ab7821115c5d8c4de9842b9/pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd", size = 11414864 }, + { url = "https://files.pythonhosted.org/packages/fe/be/d448af7d657d82e1888dd8551f79c6d6fb161080b5b9752d84d910ec2319/pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c", size = 11925105 }, + { url = "https://files.pythonhosted.org/packages/29/c1/ccb4238212c8c4f496c584f3044d94e0c030ed8e1d68999db46c91c2242f/pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea", size = 10387612 }, + { url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776 }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415 }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266 }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814 }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408 }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160 }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172 }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232 }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653 }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195 }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969 }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323 }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838 }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830 }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383 }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934 }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684 }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137 }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267 }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684 }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487 }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433 }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889 }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109 }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736 }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129 }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562 }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439 }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287 }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691 }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185 }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736 }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435 }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262 }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344 }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131 }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757 }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962 }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171 }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116 }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209 }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707 }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995 }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503 }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956 }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855 }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642 }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281 }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716 }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125 }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939 }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506 }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063 }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549 }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331 }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370 }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147 }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659 }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439 }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577 }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394 }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375 }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048 }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006 }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509 }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167 }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237 }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047 }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440 }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895 }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384 }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537 }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491 }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510 }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058 }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776 }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358 }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "polyfactory" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "faker", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/68/7717bd9e63ed254617a7d3dc9260904fb736d6ea203e58ffddcb186c64e4/polyfactory-3.3.0.tar.gz", hash = "sha256:237258b6ff43edf362ffd1f68086bb796466f786adfa002b0ac256dbf2246e9a", size = 348668 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/34/b6f19941adcdaf415b5e8a8d577499f5b6a76b59cbae37f9b125a9ffe9f2/polyfactory-3.3.0-py3-none-any.whl", hash = "sha256:686abcaa761930d3df87b91e95b26b8d8cb9fdbbbe0b03d5f918acff5c72606e", size = 62707 }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595 }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082 }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476 }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062 }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893 }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589 }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664 }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087 }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383 }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210 }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228 }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284 }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090 }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859 }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560 }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997 }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972 }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266 }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737 }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617 }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/60/a3624f79acea344c16fbef3a94d28b89a8042ddfb8f3e4ca83f538671409/psycopg2_binary-2.9.12.tar.gz", hash = "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c", size = 379686 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/19/d4ce60954f3bb9d8e3bc5e5c4d1f2487de2d3851bf2391d54954c9df12a6/psycopg2_binary-2.9.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5c8ce6c61bd1b1f6b9c24ee32211599f6166af2c55abb19456090a21fd16554b", size = 3712338 }, + { url = "https://files.pythonhosted.org/packages/53/71/c85409ee0d78890f0660eff262e815e7dd2bb741a17611d82e9e8cd9dc5e/psycopg2_binary-2.9.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4a9eaa6e7f4ff91bec10aa3fb296878e75187bced5cc4bafe17dc40915e1326", size = 3822407 }, + { url = "https://files.pythonhosted.org/packages/3c/ed/60486c2c7f0d4d1ede2bfb1ed27e2498477ce646bc7f6b2759906303117e/psycopg2_binary-2.9.12-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c6528cefc8e50fcc6f4a107e27a672058b36cc5736d665476aeb413ba88dbb06", size = 4578425 }, + { url = "https://files.pythonhosted.org/packages/0b/b9/656cb03fad9f4f49f2145c334b1126ee75189929ca4e6187d485a2d59951/psycopg2_binary-2.9.12-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4e184b1fb6072bf05388aa41c697e1b2d01b3473f107e7ec44f186a32cfd0b8", size = 4273709 }, + { url = "https://files.pythonhosted.org/packages/99/66/08cf0da0e25cc6fb142c89be45fc8418792858f0c4cbff5e24530ff02cd6/psycopg2_binary-2.9.12-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4766ab678563054d3f1d064a4db19cc4b5f9e3a8d9018592a8285cf200c248f3", size = 5893779 }, + { url = "https://files.pythonhosted.org/packages/17/d7/eecd9ce8e146d3721115d82d3836efdbb712187e4590325df549989d18f4/psycopg2_binary-2.9.12-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5a0253224780c978746cb9be55a946bcdaf40fe3519c0f622924cdabdafe2c39", size = 4109308 }, + { url = "https://files.pythonhosted.org/packages/b6/2e/b1dc289b362cc8d45697b57eefbd673186f49a4ea0906928988e3affcc98/psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0dc9228d47c46bda253d2ecd6bb93b56a9f2d7ad33b684a1fa3622bf74ffe30c", size = 3654405 }, + { url = "https://files.pythonhosted.org/packages/eb/e4/4c4aea6473214dbdbd0fbba11aa4691e76dc01722c55724c5951719865ff/psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f921f3cd87035ef7df233383011d7a53ea1d346224752c1385f1edfd790ceb6a", size = 3299187 }, + { url = "https://files.pythonhosted.org/packages/ba/5d/b03b99986446a4f57b170ed9a2579fb7ff9783ca0fa5226b19db99737fee/psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d999bd982a723113c1a45b55a7a6a90d64d0ed2278020ed625c490ff7bef96c", size = 3047716 }, + { url = "https://files.pythonhosted.org/packages/14/86/382ee4afbd1d97500c9d2862b20c2fdeddf4b7335e984df3fb4309f64108/psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29d4d134bd0ab46ffb04e94aa3c5fa3ef582e9026609165e2f758ff76fc3a3be", size = 3349237 }, + { url = "https://files.pythonhosted.org/packages/a8/16/9a57c75ba1eda7165c017342f526810d5f5a12647dde749c99ae9a7141d7/psycopg2_binary-2.9.12-cp311-cp311-win_amd64.whl", hash = "sha256:cb4a1dacdd48077150dc762a9e5ddbf32c256d66cb46f80839391aa458774936", size = 2757036 }, + { url = "https://files.pythonhosted.org/packages/e2/9f/ef4ef3c8e15083df90ca35265cfd1a081a2f0cc07bb229c6314c6af817f4/psycopg2_binary-2.9.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5cdc05117180c5fa9c40eea8ea559ce64d73824c39d928b7da9fb5f6a9392433", size = 3712459 }, + { url = "https://files.pythonhosted.org/packages/b5/01/3dd14e46ba48c1e1a6ec58ee599fa1b5efa00c246d5046cd903d0eeb1af1/psycopg2_binary-2.9.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3227a3bc228c10d21011a99245edca923e4e8bf461857e869a507d9a41fe9f6", size = 3822936 }, + { url = "https://files.pythonhosted.org/packages/a6/f7/0640e4901119d8a9f7a1784b927f494e2198e213ceb593753d1f2c8b1b30/psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:995ce929eede89db6254b50827e2b7fd61e50d11f0b116b29fffe4a2e53c4580", size = 4578676 }, + { url = "https://files.pythonhosted.org/packages/b0/55/44df3965b5f297c50cc0b1b594a31c67d6127a9d133045b8a66611b14dfb/psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9fe06d93e72f1c048e731a2e3e7854a5bfaa58fc736068df90b352cefe66f03f", size = 4274917 }, + { url = "https://files.pythonhosted.org/packages/b0/4b/74535248b1eac0c9336862e8617c765ac94dac76f9e25d7c4a79588c8907/psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40e7b28b63aaf737cb3a1edc3a9bbc9a9f4ad3dcb7152e8c1130e4050eddcb7d", size = 5894843 }, + { url = "https://files.pythonhosted.org/packages/f2/ba/f1bf8d2ae71868ad800b661099086ee52bc0f8d9f05be1acd8ebb06757cc/psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89d19a9f7899e8eb0656a2b3a08e0da04c720a06db6e0033eab5928aabe60fa9", size = 4110556 }, + { url = "https://files.pythonhosted.org/packages/45/46/c15706c338403b7c420bcc0c2905aad116cc064545686d8bf85f1999ea00/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:612b965daee295ae2da8f8218ce1d274645dc76ef3f1abf6a0a94fd57eff876d", size = 3655714 }, + { url = "https://files.pythonhosted.org/packages/b3/7c/a2d5dc09b64a4564db242a0fe418fde7d33f6f8259dd2c5b9d7def00fb5a/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b9a339b79d37c1b45f3235265f07cdeb0cb5ad7acd2ac7720a5920989c17c24e", size = 3301154 }, + { url = "https://files.pythonhosted.org/packages/c0/e8/cc8c9a4ce71461f9ec548d38cadc41dc184b34c73e6455450775a9334ccd/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3471336e1acfd9c7fe507b8bad5af9317b6a89294f9eb37bd9a030bb7bebcdc6", size = 3048882 }, + { url = "https://files.pythonhosted.org/packages/19/6a/31e2296bc0787c5ab75d3d118e40b239db8151b5192b90b77c72bc9256e9/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7af18183109e23502c8b2ae7f6926c0882766f35b5175a4cd737ad825e4d7a1b", size = 3351298 }, + { url = "https://files.pythonhosted.org/packages/5f/a8/75f4e3e11203b590150abed2cf7794b9c9c9f7eceddae955191138b44dde/psycopg2_binary-2.9.12-cp312-cp312-win_amd64.whl", hash = "sha256:398fcd4db988c7d7d3713e2b8e18939776fd3fb447052daae4f24fa39daede4c", size = 2757230 }, + { url = "https://files.pythonhosted.org/packages/91/bb/4608c96f970f6e0c56572e87027ef4404f709382a3503e9934526d7ba051/psycopg2_binary-2.9.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7c729a73c7b1b84de3582f73cdd27d905121dc2c531f3d9a3c32a3011033b965", size = 3712419 }, + { url = "https://files.pythonhosted.org/packages/5e/af/48f76af9d50d61cf390f8cd657b503168b089e2e9298e48465d029fcc713/psycopg2_binary-2.9.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4413d0caef93c5cf50b96863df4c2efe8c269bf2267df353225595e7e15e8df7", size = 3822990 }, + { url = "https://files.pythonhosted.org/packages/7a/df/aba0f99397cd811d32e06fc0cc781f1f3ce98bc0e729cb423925085d781a/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4dfcf8e45ebb0c663be34a3442f65e17311f3367089cd4e5e3a3e8e62c978777", size = 4578696 }, + { url = "https://files.pythonhosted.org/packages/95/9c/eaa74021ac4e4d5c2f83d82fc6615a63f4fe6c94dc4e94c3990427053f67/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c41321a14dd74aceb6a9a643b9253a334521babfa763fa873e33d89cfa122fb5", size = 4274982 }, + { url = "https://files.pythonhosted.org/packages/35/ed/c25deff98bd26187ba48b3b250a3ffc3037c46c5b89362534a15d200e0db/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83946ba43979ebfdc99a3cd0ee775c89f221df026984ba19d46133d8d75d3cd9", size = 5894867 }, + { url = "https://files.pythonhosted.org/packages/9a/81/8d0e21ca77373c6c9589e5c4528f6e8f0c08c62cafc76fb0bddb7a2cee22/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:411e85815652d13560fbe731878daa5d92378c4995a22302071890ec3397d019", size = 4110578 }, + { url = "https://files.pythonhosted.org/packages/00/fc/f481e2435bd8f742d0123309174aae4165160ad3ef17c1b99c3622c241d2/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c8ad4c08e00f7679559eaed7aff1edfffc60c086b976f93972f686384a95e2c", size = 3655816 }, + { url = "https://files.pythonhosted.org/packages/53/79/b9f46466bdbe9f239c96cde8be33c1aace4842f06013b47b730dc9759187/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:00814e40fa23c2b37ef0a1e3c749d89982c73a9cb5046137f0752a22d432e82f", size = 3301307 }, + { url = "https://files.pythonhosted.org/packages/3f/19/7dc003b32fe35024df89b658104f7c8538a8b2dcbde7a4e746ce929742e7/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:98062447aebc20ed20add1f547a364fd0ef8933640d5372ff1873f8deb9b61be", size = 3048968 }, + { url = "https://files.pythonhosted.org/packages/91/58/2dbd7db5c604d45f4950d988506aae672a14126ec22998ced5021cbb76bb/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66a7685d7e548f10fb4ce32fb01a7b7f4aa702134de92a292c7bd9e0d3dbd290", size = 3351369 }, + { url = "https://files.pythonhosted.org/packages/42/ee/dee8dcaad07f735824de3d6563bc67119fa6c28257b17977a8d624f02fab/psycopg2_binary-2.9.12-cp313-cp313-win_amd64.whl", hash = "sha256:b6937f5fe4e180aeee87de907a2fa982ded6f7f15d7218f78a083e4e1d68f2a0", size = 2757347 }, + { url = "https://files.pythonhosted.org/packages/13/1b/708c0dca874acfad6d65314271859899a79007686f3a1f74e82a2ed4b645/psycopg2_binary-2.9.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f3b3de8a74ef8db215f22edffb19e32dc6fa41340456de7ec99efdc8a7b3ec2", size = 3712428 }, + { url = "https://files.pythonhosted.org/packages/d6/39/ddbea9d4b4de6aca9431b6ed253f530f8a02d3b8f9bcfd0dbfe2b3de6fe4/psycopg2_binary-2.9.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1006fb62f0f0bc5ce256a832356c6262e91be43f5e4eb15b5eaf38079464caf2", size = 3823184 }, + { url = "https://files.pythonhosted.org/packages/bf/a0/bc2fef74b106fa345567122a0659e6d94512ed7dc0131ec44c9e5aba3725/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:840066105706cd2eb29b9a1c2329620056582a4bf3e8169dec5c447042d0869f", size = 4579157 }, + { url = "https://files.pythonhosted.org/packages/57/d7/d4e3b2005d3de607ca4fbb0e8742e248056e52184a6b94ebda3c1c2c329b/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:863f5d12241ebe1c76a72a04c2113b6dc905f90b9cef0e9be0efd994affd9354", size = 4274970 }, + { url = "https://files.pythonhosted.org/packages/2e/42/c9853f8db3967fe08bcde11f53d53b85d351750cae726ce001cb68afa9c1/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a99eaab34a9010f1a086b126de467466620a750634d114d20455f3a824aae033", size = 5895175 }, + { url = "https://files.pythonhosted.org/packages/eb/fd/b82b5601a97630308bef079f545ffec481bbbc795c2ba5ec416a01d03f60/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ffdd7dc5463ccd61845ac37b7012d0f35a1548df9febe14f8dd549be4a0bc81e", size = 4110658 }, + { url = "https://files.pythonhosted.org/packages/62/8c/32ca69b0389ef25dd22937bf9e8fbe2ce27aea20b05ded48c4ce4cb42475/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54a0dfecab1b48731f934e06139dfe11e24219fb6d0ceb32177cf0375f14c7b5", size = 3656251 }, + { url = "https://files.pythonhosted.org/packages/c4/29/96992a2b59e3b9d730fcf9612d0a387305025dc867a9fc490a9e496e074e/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:96937c9c5d891f772430f418a7a8b4691a90c3e6b93cf72b5bd7cad8cbca32a5", size = 3301810 }, + { url = "https://files.pythonhosted.org/packages/56/ad/44b06659949b243ae10112cd3b20a197f9bf3e81d5651379b9eb889bfaad/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:77b348775efd4cdab410ec6609d81ccecd1139c90265fa583a7255c8064bc03d", size = 3048977 }, + { url = "https://files.pythonhosted.org/packages/1d/f2/10a1bcebadb6aa55e280e1f58975c36a7b560ea525184c7aa4064c466633/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:527e6342b3e44c2f0544f6b8e927d60de7f163f5723b8f1dfa7d2a84298738cd", size = 3351466 }, + { url = "https://files.pythonhosted.org/packages/20/be/b732c8418ffa5bcfda002890f5dc4c869fc17db66ff11f53b17cfe44afc0/psycopg2_binary-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980", size = 2848762 }, +] + +[[package]] +name = "pyclipper" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/21/3c06205bb407e1f79b73b7b4dfb3950bd9537c4f625a68ab5cc41177f5bc/pyclipper-1.4.0.tar.gz", hash = "sha256:9882bd889f27da78add4dd6f881d25697efc740bf840274e749988d25496c8e1", size = 54489 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/e3/64cf7794319b088c288706087141e53ac259c7959728303276d18adc665d/pyclipper-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:adcb7ca33c5bdc33cd775e8b3eadad54873c802a6d909067a57348bcb96e7a2d", size = 264281 }, + { url = "https://files.pythonhosted.org/packages/34/cd/44ec0da0306fa4231e76f1c2cb1fa394d7bde8db490a2b24d55b39865f69/pyclipper-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd24849d2b94ec749ceac7c34c9f01010d23b6e9d9216cf2238b8481160e703d", size = 139426 }, + { url = "https://files.pythonhosted.org/packages/ad/88/d8f6c6763ea622fe35e19c75d8b39ed6c55191ddc82d65e06bc46b26cb8e/pyclipper-1.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b6c8d75ba20c6433c9ea8f1a0feb7e4d3ac06a09ad1fd6d571afc1ddf89b869", size = 989649 }, + { url = "https://files.pythonhosted.org/packages/ff/e9/ea7d68c8c4af3842d6515bedcf06418610ad75f111e64c92c1d4785a1513/pyclipper-1.4.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58e29d7443d7cc0e83ee9daf43927730386629786d00c63b04fe3b53ac01462c", size = 962842 }, + { url = "https://files.pythonhosted.org/packages/4e/b7/0b4a272d8726e51ab05e2b933d8cc47f29757fb8212e38b619e170e6015c/pyclipper-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a8d2b5fb75ebe57e21ce61e79a9131edec2622ff23cc665e4d1d1f201bc1a801", size = 95098 }, + { url = "https://files.pythonhosted.org/packages/3a/76/4901de2919198bb2bd3d989f86d4a1dff363962425bb2d63e24e6c990042/pyclipper-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:e9b973467d9c5fa9bc30bb6ac95f9f4d7c3d9fc25f6cf2d1cc972088e5955c01", size = 104362 }, + { url = "https://files.pythonhosted.org/packages/90/1b/7a07b68e0842324d46c03e512d8eefa9cb92ba2a792b3b4ebf939dafcac3/pyclipper-1.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:222ac96c8b8281b53d695b9c4fedc674f56d6d4320ad23f1bdbd168f4e316140", size = 265676 }, + { url = "https://files.pythonhosted.org/packages/6b/dd/8bd622521c05d04963420ae6664093f154343ed044c53ea260a310c8bb4d/pyclipper-1.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f3672dbafbb458f1b96e1ee3e610d174acb5ace5bd2ed5d1252603bb797f2fc6", size = 140458 }, + { url = "https://files.pythonhosted.org/packages/7a/06/6e3e241882bf7d6ab23d9c69ba4e85f1ec47397cbbeee948a16cf75e21ed/pyclipper-1.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1f807e2b4760a8e5c6d6b4e8c1d71ef52b7fe1946ff088f4fa41e16a881a5ca", size = 978235 }, + { url = "https://files.pythonhosted.org/packages/cf/f4/3418c1cd5eea640a9fa2501d4bc0b3655fa8d40145d1a4f484b987990a75/pyclipper-1.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce1f83c9a4e10ea3de1959f0ae79e9a5bd41346dff648fee6228ba9eaf8b3872", size = 961388 }, + { url = "https://files.pythonhosted.org/packages/ac/94/c85401d24be634af529c962dd5d781f3cb62a67cd769534df2cb3feee97a/pyclipper-1.4.0-cp312-cp312-win32.whl", hash = "sha256:3ef44b64666ebf1cb521a08a60c3e639d21b8c50bfbe846ba7c52a0415e936f4", size = 95169 }, + { url = "https://files.pythonhosted.org/packages/97/77/dfea08e3b230b82ee22543c30c35d33d42f846a77f96caf7c504dd54fab1/pyclipper-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:d1e5498d883b706a4ce636247f0d830c6eb34a25b843a1b78e2c969754ca9037", size = 104619 }, + { url = "https://files.pythonhosted.org/packages/67/d0/cbce7d47de1e6458f66a4d999b091640134deb8f2c7351eab993b70d2e10/pyclipper-1.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d49df13cbb2627ccb13a1046f3ea6ebf7177b5504ec61bdef87d6a704046fd6e", size = 264342 }, + { url = "https://files.pythonhosted.org/packages/ce/cc/742b9d69d96c58ac156947e1b56d0f81cbacbccf869e2ac7229f2f86dc4e/pyclipper-1.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37bfec361e174110cdddffd5ecd070a8064015c99383d95eb692c253951eee8a", size = 139839 }, + { url = "https://files.pythonhosted.org/packages/db/48/dd301d62c1529efdd721b47b9e5fb52120fcdac5f4d3405cfc0d2f391414/pyclipper-1.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14c8bdb5a72004b721c4e6f448d2c2262d74a7f0c9e3076aeff41e564a92389f", size = 972142 }, + { url = "https://files.pythonhosted.org/packages/07/bf/d493fd1b33bb090fa64e28c1009374d5d72fa705f9331cd56517c35e381e/pyclipper-1.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2a50c22c3a78cb4e48347ecf06930f61ce98cf9252f2e292aa025471e9d75b1", size = 952789 }, + { url = "https://files.pythonhosted.org/packages/cf/88/b95ea8ea21ddca34aa14b123226a81526dd2faaa993f9aabd3ed21231604/pyclipper-1.4.0-cp313-cp313-win32.whl", hash = "sha256:c9a3faa416ff536cee93417a72bfb690d9dea136dc39a39dbbe1e5dadf108c9c", size = 94817 }, + { url = "https://files.pythonhosted.org/packages/ba/42/0a1920d276a0e1ca21dc0d13ee9e3ba10a9a8aa3abac76cd5e5a9f503306/pyclipper-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:d4b2d7c41086f1927d14947c563dfc7beed2f6c0d9af13c42fe3dcdc20d35832", size = 104007 }, + { url = "https://files.pythonhosted.org/packages/1a/20/04d58c70f3ccd404f179f8dd81d16722a05a3bf1ab61445ee64e8218c1f8/pyclipper-1.4.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7c87480fc91a5af4c1ba310bdb7de2f089a3eeef5fe351a3cedc37da1fcced1c", size = 265167 }, + { url = "https://files.pythonhosted.org/packages/bd/2e/a570c1abe69b7260ca0caab4236ce6ea3661193ebf8d1bd7f78ccce537a5/pyclipper-1.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81d8bb2d1fb9d66dc7ea4373b176bb4b02443a7e328b3b603a73faec088b952e", size = 139966 }, + { url = "https://files.pythonhosted.org/packages/e8/3b/e0859e54adabdde8a24a29d3f525ebb31c71ddf2e8d93edce83a3c212ffc/pyclipper-1.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:773c0e06b683214dcfc6711be230c83b03cddebe8a57eae053d4603dd63582f9", size = 968216 }, + { url = "https://files.pythonhosted.org/packages/f6/6b/e3c4febf0a35ae643ee579b09988dd931602b5bf311020535fd9e5b7e715/pyclipper-1.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bc45f2463d997848450dbed91c950ca37c6cf27f84a49a5cad4affc0b469e39", size = 954198 }, + { url = "https://files.pythonhosted.org/packages/fc/74/728efcee02e12acb486ce9d56fa037120c9bf5b77c54bbdbaa441c14a9d9/pyclipper-1.4.0-cp314-cp314-win32.whl", hash = "sha256:0b8c2105b3b3c44dbe1a266f64309407fe30bf372cf39a94dc8aaa97df00da5b", size = 96951 }, + { url = "https://files.pythonhosted.org/packages/e3/d7/7f4354e69f10a917e5c7d5d72a499ef2e10945312f5e72c414a0a08d2ae4/pyclipper-1.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c317e182590c88ec0194149995e3d71a979cfef3b246383f4e035f9d4a11826", size = 106782 }, + { url = "https://files.pythonhosted.org/packages/63/60/fc32c7a3d7f61a970511ec2857ecd09693d8ac80d560ee7b8e67a6d268c9/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f160a2c6ba036f7eaf09f1f10f4fbfa734234af9112fb5187877efed78df9303", size = 269880 }, + { url = "https://files.pythonhosted.org/packages/49/df/c4a72d3f62f0ba03ec440c4fff56cd2d674a4334d23c5064cbf41c9583f6/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9f11ad133257c52c40d50de7a0ca3370a0cdd8e3d11eec0604ad3c34ba549e9", size = 141706 }, + { url = "https://files.pythonhosted.org/packages/c5/0b/cf55df03e2175e1e2da9db585241401e0bc98f76bee3791bed39d0313449/pyclipper-1.4.0-cp314-cp314t-win32.whl", hash = "sha256:bbc827b77442c99deaeee26e0e7f172355ddb097a5e126aea206d447d3b26286", size = 105308 }, + { url = "https://files.pythonhosted.org/packages/8f/dc/53df8b6931d47080b4fe4ee8450d42e660ee1c5c1556c7ab73359182b769/pyclipper-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29dae3e0296dff8502eeb7639fcfee794b0eec8590ba3563aee28db269da6b04", size = 117608 }, + { url = "https://files.pythonhosted.org/packages/18/59/81050abdc9e5b90ffc2c765738c5e40e9abd8e44864aaa737b600f16c562/pyclipper-1.4.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98b2a40f98e1fc1b29e8a6094072e7e0c7dfe901e573bf6cfc6eb7ce84a7ae87", size = 126495 }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pydantic-core", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-inspection", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262 }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872 }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255 }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827 }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051 }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314 }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146 }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685 }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420 }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122 }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573 }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139 }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433 }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513 }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114 }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298 }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158 }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724 }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742 }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418 }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274 }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940 }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516 }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854 }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306 }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044 }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133 }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464 }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823 }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919 }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604 }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306 }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906 }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802 }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446 }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757 }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275 }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467 }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417 }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782 }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782 }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334 }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986 }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693 }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819 }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411 }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079 }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179 }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926 }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785 }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733 }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534 }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732 }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627 }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141 }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325 }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990 }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978 }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354 }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238 }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251 }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593 }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226 }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605 }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777 }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641 }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404 }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219 }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594 }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542 }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146 }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309 }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736 }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575 }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624 }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325 }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589 }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552 }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984 }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417 }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527 }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024 }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696 }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590 }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782 }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146 }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492 }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604 }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828 }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000 }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286 }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071 }, +] + +[[package]] +name = "pydantic-settings" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "python-dotenv", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-inspection", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413 }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147 }, +] + +[[package]] +name = "pylatexenc" +version = "2.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/45/ddb0fb04acf95fe9cf9c369814dbdd08651bd2c9ee455f142651e06f4488/pylatexenc-2.11.tar.gz", hash = "sha256:305a072a99ce736246049c9da05841b9d718c0f7ea8888f5f596cf15cb621053", size = 165743 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/06/3d67bd912ef337aa4856b466121d03f304aa1bb4d804f9298b6227cd227e/pylatexenc-2.11-py2.py3-none-any.whl", hash = "sha256:e78e7391d6c104f1ed150e21cfaa58016cdb50aa54406a2eecb793649ffdfdd0", size = 137533 }, +] + +[[package]] +name = "pypdfium2" +version = "5.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/78/a52cb80611339ec95f35c7a10d7bfe7a6f97f3b50a35a9f94283d062512e/pypdfium2-5.13.0.tar.gz", hash = "sha256:7ca2d8e31bd8d0d40c496416b7d8bea423388669ffd494929f50e8c3a82326b8", size = 273639 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/9c/a49050af85055054299c7fab658ac63f8fddde575774aecbf8f71c7a9e5f/pypdfium2-5.13.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:882f4bbd4b17a335b43603169a14cde9341de12b238acd5c39e690cbca7c4293", size = 3417299 }, + { url = "https://files.pythonhosted.org/packages/50/ad/f23027328843ee2bdd05afe16bb101f5906befd0c70de35fa8c53f60a5ff/pypdfium2-5.13.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:d96929bde3bd64c771ab3558ca1ffd7704cc4d872ab92cd9f8f8b8a20f7f36b8", size = 2864708 }, + { url = "https://files.pythonhosted.org/packages/08/99/1fe58428b69d2722dcbcfaa08ce71834a332c5b518fd58874bcef936b823/pypdfium2-5.13.0-py3-none-macosx_13_0_arm64.whl", hash = "sha256:da5c7b74eebf40b5c1fbe1de01aa1edc8827a79fb1efd999616bc20dcaf77ba4", size = 3507415 }, + { url = "https://files.pythonhosted.org/packages/9f/41/06e26da88a4f5b4ed289325868717a186020661b7b221aa6df622711d31b/pypdfium2-5.13.0-py3-none-macosx_13_0_x86_64.whl", hash = "sha256:2abedfb5c70992b19c780ed58d7f7b929e8ce8ee52c9140158f44317c90ec6c7", size = 3670979 }, + { url = "https://files.pythonhosted.org/packages/fe/31/f8210d53775f142be934336665b1d60e800c3f176f28c29b4908d945c518/pypdfium2-5.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ee8c2bb2e68b396ab4a763215ac100dacb6b96d0da5bebeb239a021aecc3a7e", size = 3676486 }, + { url = "https://files.pythonhosted.org/packages/94/50/d339fa09fbe592564b100bfc76833170a1104a764a458ac2abfffcb632f2/pypdfium2-5.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07f58e91b8c45ca144a1ff3008faf3c73ef8a5e9fb32988831788363288228cd", size = 3400883 }, + { url = "https://files.pythonhosted.org/packages/c3/e0/b10cf41b5e9f0212d014c40635659c6ab95bb4fcc6fc47f5d3c571f8d57f/pypdfium2-5.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46b2f5be9e7ae941ee4216e3d20b66f9dc3d81944a3d57756272de5275204709", size = 3803912 }, + { url = "https://files.pythonhosted.org/packages/a7/d8/25ba4ce9a9059ece82f4514df0658fde0aa9bbeafe135e76017c052bf56f/pypdfium2-5.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d96beb7f379e6c76d874ca93fcd182ac3168dd499056407070f9927fb1061b8e", size = 4218231 }, + { url = "https://files.pythonhosted.org/packages/d3/7c/74a2fb48e5b0d2402d9ca64b39074c722d67e9a8a2c58449a843a8c2329a/pypdfium2-5.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81df25c1ab4c13ff773102d3cbea1967511d079123b067fc077bd0c4d57d91d8", size = 3730077 }, + { url = "https://files.pythonhosted.org/packages/59/12/8c922f00518c26dc47d3676cc09c1d3c95e991c1977e31067d23cc2215cb/pypdfium2-5.13.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d66a32d89fa5b4a2715810171239eb194df4aba604727483ab760512f3c6a851", size = 4031512 }, + { url = "https://files.pythonhosted.org/packages/c6/48/a171d034c2dac01adcc57d3dad3c97ba11f19d916f421176002c9e02c904/pypdfium2-5.13.0-py3-none-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b90b0a5ac310bb34db8eb848e58fcab4e201e124e3cf3cb1ccb7b85293e034af", size = 3995485 }, + { url = "https://files.pythonhosted.org/packages/36/2e/dcb24776d409bb9e5b7fb26a0c62a87b98ab0e30dfcca645eaf31e35123b/pypdfium2-5.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ada81c36483cd61d07e32bc7814620ee96256b4f421b913f566861bf91800248", size = 5016636 }, + { url = "https://files.pythonhosted.org/packages/93/24/1fab8470fc6de6f4481f009c90757b1a1ee0a61d8e864ed273f72ffca855/pypdfium2-5.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3826e521e895648983cb9ee6b934d4bf51552600043984f84e9c2b3b14b696f3", size = 4555251 }, + { url = "https://files.pythonhosted.org/packages/cd/ef/6e8dbea1eddcb55cf34172753ffccd39566333c803cc94d43c653f369f2f/pypdfium2-5.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5c029d7163a91f264eafab51fb442a84a33efd9fd83d5a06c0136a7857a3cc8d", size = 5263483 }, + { url = "https://files.pythonhosted.org/packages/53/fe/2ff673730189a621c01f9193c74b0f6aa70d8740889fdf11949e1c541869/pypdfium2-5.13.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:be2dccbde0ce7efe334ecd8f348df4308db360756ede4f0821d82dfc9a58caa8", size = 5144135 }, + { url = "https://files.pythonhosted.org/packages/19/0b/759b9037c007317fa5c990dd3f6eff2b99d3fbced251d1e2512be92f2e2e/pypdfium2-5.13.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:bcd81394fe101405e026eedb3e40bef84635c1e5d974dd6036420eb6937753c6", size = 4648156 }, + { url = "https://files.pythonhosted.org/packages/db/3b/ffe29679c52efe8eb02d77aa6656e6d6201395423329af018ebd5923a3d0/pypdfium2-5.13.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:2ed32ff685f8e05e637c990bedbf5fca66727bf27718d8bc33eeab21ce0630d1", size = 5089852 }, + { url = "https://files.pythonhosted.org/packages/7b/b6/cebacc1601ddfdcd1e6a1dc321533d215ceccf9b825fa9b91b11c6dc39fb/pypdfium2-5.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9c777edba28d1d5fd15435ed3a78ee2fdb93dd069be37cb53b559bc122793770", size = 5074153 }, + { url = "https://files.pythonhosted.org/packages/54/40/cf14c4f534f817788966857afdedb90002198dca5ce4fe2c6ecb031955ae/pypdfium2-5.13.0-py3-none-win32.whl", hash = "sha256:d33ee7077db67478b75efe4b5ea9610fb96c5416a0bc4949227f0f59c34dfcd9", size = 3753164 }, + { url = "https://files.pythonhosted.org/packages/5d/99/a37b6b902457569468ed5908c94e56cb6c4032541f02cf89f723d42a9148/pypdfium2-5.13.0-py3-none-win_amd64.whl", hash = "sha256:47dcca2a8d507b5fd24f94c3c9d48fb379430f097bc20f01beff6c963ffbcedb", size = 3885553 }, + { url = "https://files.pythonhosted.org/packages/50/7f/d39f6e64375c2ffd50ea100e3c73af79085c880c2791eb7203bc61d8913f/pypdfium2-5.13.0-py3-none-win_arm64.whl", hash = "sha256:554a0b23376460af1410e3c915906895e2dac67a086b9e6ccde0643a795d3b0d", size = 3700026 }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system != 'Linux' and sys_platform == 'win32'" }, + { name = "iniconfig", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "packaging", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pluggy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pygments", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536 }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930 }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"], marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pluggy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pytest", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-docx" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987 }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780 }, +] + +[[package]] +name = "python-json-logger" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/25/5473e46b179f8e8b4ad3aeeb36773d1701b7770eaf5e5bc2025c7303b598/python_json_logger-4.2.0.tar.gz", hash = "sha256:e371ebe22ec01e289850102091a2b1f6fc9e655c7f1f5f29073936756c290afa", size = 18211 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/55/6467fde553886cb293e41538f3a8b4e4fd4688c6df242cf982162d8367fb/python_json_logger-4.2.0-py3-none-any.whl", hash = "sha256:158a52126fcd6869e09574d2b66272666f3dc8f468c62637ef9a1fa883719cb9", size = 14988 }, +] + +[[package]] +name = "python-oxmsg" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "olefile", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/4e/869f34faedbc968796d2c7e9837dede079c9cb9750917356b1f1eda926e9/python_oxmsg-0.0.2.tar.gz", hash = "sha256:a6aff4deb1b5975d44d49dab1d9384089ffeec819e19c6940bc7ffbc84775fad", size = 34713 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/67/f56c69a98c7eb244025845506387d0f961681657c9fcd8b2d2edd148f9d2/python_oxmsg-0.0.2-py3-none-any.whl", hash = "sha256:22be29b14c46016bcd05e34abddfd8e05ee82082f53b82753d115da3fc7d0355", size = 31455 }, +] + +[[package]] +name = "python-pptx" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pillow", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "xlsxwriter", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788 }, +] + +[[package]] +name = "pywin32" +version = "312" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/f5/10a6e845a00fc5e7afd0a988b744f403d4d57162a28d160a093c4d9322f0/pywin32-312-cp311-cp311-win32.whl", hash = "sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c", size = 6362659 }, + { url = "https://files.pythonhosted.org/packages/35/c4/dcd2d62b5944b6d5db53413a5899016ccd57ffcb7278f3f81655d25d2027/pywin32-312-cp311-cp311-win_amd64.whl", hash = "sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a", size = 6928825 }, + { url = "https://files.pythonhosted.org/packages/b7/56/3cbb433fe4501cdba2eb9040f56a4e1a8243faa4186b25295564d1a7a79d/pywin32-312-cp311-cp311-win_arm64.whl", hash = "sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47", size = 6721875 }, + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877 }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841 }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901 }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184 }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298 }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640 }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928 }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157 }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598 }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159 }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293 }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826 }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577 }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556 }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114 }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638 }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463 }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986 }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543 }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763 }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, +] + +[[package]] +name = "rapidocr" +version = "3.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorlog", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "omegaconf", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "opencv-python", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pillow", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pyclipper", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pyyaml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "requests", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "shapely", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "six", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tqdm", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/ed/0ee9b9281986974be9d2406ae0134c8d7c91d2fc613f16ffda9701eeda6f/rapidocr-3.9.2-py3-none-any.whl", hash = "sha256:04d6b8d151f823d930bd91910555f57bea897c0c44fa6794267b94cf9c1ef9a0", size = 27275208 }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "rpds-py", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 }, +] + +[[package]] +name = "regex" +version = "2026.7.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e5/cef4de2bac939280b68d32adc659478845238a8274f2f79c465063f590ad/regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c", size = 494012 }, + { url = "https://files.pythonhosted.org/packages/ff/87/e86f51eb117457bb7803132ffe5cb6e2841e2b5bea4cc85d397f3c6e257d/regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae", size = 295281 }, + { url = "https://files.pythonhosted.org/packages/41/2e/2360c41d8080a3d9ec7e5c90fad6eab3b50192869d10e9a5609e48c8177b/regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5", size = 290615 }, + { url = "https://files.pythonhosted.org/packages/cf/69/b65ba4344efbc771b28fe5dde84cbbb6c8f9551165952fe78def5b9dde6a/regex-2026.7.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20", size = 791804 }, + { url = "https://files.pythonhosted.org/packages/81/b6/a40dfa0dc6224b36f620c00296eacc830489cbf8c2837b6750dfe6170375/regex-2026.7.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a", size = 861723 }, + { url = "https://files.pythonhosted.org/packages/e3/02/735991dee71abd83196a7962f7ed8bf5aa05720ff06e2d3ff896a85e2bbb/regex-2026.7.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7", size = 905932 }, + { url = "https://files.pythonhosted.org/packages/45/6c/e7098d8b846ccdbf431d8c081b61e496526a27a28094ed09e0dce21b3f54/regex-2026.7.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc", size = 801407 }, + { url = "https://files.pythonhosted.org/packages/8a/18/34b69274e2649bcc7d9b089c2b2983fb2632d8ecf667e359593be9072e79/regex-2026.7.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864", size = 774448 }, + { url = "https://files.pythonhosted.org/packages/bb/e6/0a72247d025585fd3800b98e040b84d562a88af6303347100484849f4f01/regex-2026.7.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af", size = 783297 }, + { url = "https://files.pythonhosted.org/packages/b1/aa/c4f65ae7dd02a36b323a70c4cff326e1f3442361aaebc9311100a130d54f/regex-2026.7.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5", size = 854736 }, + { url = "https://files.pythonhosted.org/packages/62/c3/668082bcc817b9e694189b84997aeba7385b7779faa6711788679c482e35/regex-2026.7.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82", size = 763298 }, + { url = "https://files.pythonhosted.org/packages/4b/fb/2d07ad555e7af88aa5f867fdafa47a8d945ee237c20af3ebceb46a820835/regex-2026.7.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78", size = 844430 }, + { url = "https://files.pythonhosted.org/packages/51/15/c82a471fe3dce56f03745635b43aa456c40dc0db089e07ef148b331507d1/regex-2026.7.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327", size = 789683 }, + { url = "https://files.pythonhosted.org/packages/b5/f4/7532a2c59d56f5398902c20de60f0c9a5d1cd364e42a051b48e1b210be7b/regex-2026.7.19-cp311-cp311-win32.whl", hash = "sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d", size = 266778 }, + { url = "https://files.pythonhosted.org/packages/83/2b/cf1bc631db154eb95520d9d5dbc2371ff77a0f014bbf7d748fed8496aa63/regex-2026.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965", size = 277983 }, + { url = "https://files.pythonhosted.org/packages/8d/bd/56ceaf170e875d5a6761bf2bfd0d040f1cacc896850d5e40cb29b11bbd06/regex-2026.7.19-cp311-cp311-win_arm64.whl", hash = "sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e", size = 276961 }, + { url = "https://files.pythonhosted.org/packages/3b/b9/d11d7e501ac8fd7d617684423ebb9561e0b998481c1e4cbc0cb212c5d74a/regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d", size = 496778 }, + { url = "https://files.pythonhosted.org/packages/3f/a9/a5ab6f312f24318019170dc485d5421fe4f89e43a98640da50d95a8a7041/regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd", size = 297122 }, + { url = "https://files.pythonhosted.org/packages/b3/63/4cab4d7f2d384a144d420b763d97674cb70619c878ea6fcd7640d0e62143/regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6", size = 292009 }, + { url = "https://files.pythonhosted.org/packages/22/85/102a81b218298957d4ea7d2f084fae537a71add9d6ff93c8e67284c5f45e/regex-2026.7.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797", size = 796708 }, + { url = "https://files.pythonhosted.org/packages/78/b5/dc136af5629938a037cd2b304c12240e132ec92f38be8ff9cc89af2a1f2d/regex-2026.7.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18", size = 865651 }, + { url = "https://files.pythonhosted.org/packages/e0/75/67402ae3cd9c8c988a4c805d15ee3eef015e7ca4cb112cf3e640fc1f4153/regex-2026.7.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511", size = 911756 }, + { url = "https://files.pythonhosted.org/packages/2a/8e/096d00c7c480ef2ff4265349b14e2261d4ab787ba1f74e2e80d1c58079c3/regex-2026.7.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68", size = 801798 }, + { url = "https://files.pythonhosted.org/packages/f0/41/e7ecac6edb5722417f85cc67eaf386322fbe8acf6918ec2fdc37c20dd9d0/regex-2026.7.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11", size = 776933 }, + { url = "https://files.pythonhosted.org/packages/6f/69/03c9b3f058d66403e0ca2c938696e81d51cd4c6d47ec5265f02f96948d9a/regex-2026.7.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986", size = 784338 }, + { url = "https://files.pythonhosted.org/packages/f6/f7/b38ab3d43f284afbb618fcd15d0e77eb786ae461ce1f6bc7494619ddc0f2/regex-2026.7.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b", size = 860452 }, + { url = "https://files.pythonhosted.org/packages/15/5c/ff60ef0571121714f3cf9920bc183071e384a10b556d042e0fdb06cc07a5/regex-2026.7.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb", size = 765958 }, + { url = "https://files.pythonhosted.org/packages/aa/0f/bd34021162c0ab47f9a315bd56cd5642e920c8e5668a75ef6c6a6fca590d/regex-2026.7.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035", size = 851765 }, + { url = "https://files.pythonhosted.org/packages/2a/20/a2ca43edade0595cccfdc98636739f536d9e26898e7dbddc2b9e98898953/regex-2026.7.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a", size = 789714 }, + { url = "https://files.pythonhosted.org/packages/5d/47/e02db4015d424fc83c00ea0ac8c5e5ec14397943de9abf909d5ce3a25931/regex-2026.7.19-cp312-cp312-win32.whl", hash = "sha256:ddd67571c10869f65a5d7dde536d1e066e306cc90de57d7de4d5f34802428bb5", size = 267157 }, + { url = "https://files.pythonhosted.org/packages/08/8e/c780c131f79b42ed22d1bd7da4096c2c35f813e835acd02ef0f018bd892c/regex-2026.7.19-cp312-cp312-win_amd64.whl", hash = "sha256:e30d40268a28d54ce0437031750497004c22602b8e3ab891f759b795a003b312", size = 277777 }, + { url = "https://files.pythonhosted.org/packages/3e/4c/e4d7e086449bdf379d89774bf1f89dc4a41943f3c5a6125a03905b34b5fb/regex-2026.7.19-cp312-cp312-win_arm64.whl", hash = "sha256:de9208bb427130c82a5dbfd104f92c8876fc9559278c880b3002755bbbe9c83d", size = 277136 }, + { url = "https://files.pythonhosted.org/packages/5d/3d/84165e4299ff76f3a40fe1f2abf939e976f693383a08d2beea6af62bd2c1/regex-2026.7.19-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f035d9dc1d25eff9d361456572231c7d27b5ccd473ca7dc0adfce732bd006d40", size = 496552 }, + { url = "https://files.pythonhosted.org/packages/02/a2/a65293e6e4cf28eb7ee1be5335a5386c40d6742e9f47fafc8fec785e16c7/regex-2026.7.19-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42572142ed0b9d5d261ba727157c426510da78e20828b66bbb855098b8a4e38", size = 296983 }, + { url = "https://files.pythonhosted.org/packages/95/47/2d0564e93d87bc48618360ddca232a2ca612bbdf53ce8465d45ca5ce14ee/regex-2026.7.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40b34dd88658e4fedd2fddbf0275ac970d00614b731357f425722a3ed1983d11", size = 291832 }, + { url = "https://files.pythonhosted.org/packages/07/cd/42dfbabff3dfc9603c501c0e2e2c5adbb09d127b267bf5348de0af338c15/regex-2026.7.19-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c41c63992bf1874cebb6e7f56fd7d3c007924659a604ae3d90e427d40d4fd13", size = 796775 }, + { url = "https://files.pythonhosted.org/packages/df/5d/f6a4839f2b934e3eed5973fd07f5929ee97d4c98939fb275ea23c274ee16/regex-2026.7.19-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d3372064506b94dd2c67c845f2db8062e9e9ba84d04e33cb96d7d33c11fe1ae", size = 865687 }, + { url = "https://files.pythonhosted.org/packages/14/b0/b47d6c36049bc59806a50bd4c86ced70bbe058d787f80281b1d7a9b0e024/regex-2026.7.19-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fce7760bf283405b2c7999cab3da4e72f7deca6396013115e3f7a955db9760da", size = 911962 }, + { url = "https://files.pythonhosted.org/packages/2a/be/ff61f28f9273658cfe23acbbac5217221f6519960ed401e61dfdab12bc35/regex-2026.7.19-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0d702548d89d572b2929879bc883bb7a4c4709efafe4512cadee56c55c9bd15", size = 801817 }, + { url = "https://files.pythonhosted.org/packages/c3/bb/8b4f7f26b333f9f79e1b453613c39bb4776f51d38ae66dd0ba31d6b354ca/regex-2026.7.19-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d446c6ac40bb6e05025ccee55b84d80fe9bf8e93010ffc4bb9484f13d498835f", size = 776908 }, + { url = "https://files.pythonhosted.org/packages/09/13/610110fc5921d380516d03c26b652555f08aa0d23ea78a771231873c3638/regex-2026.7.19-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c3501bfa814ab07b5580741f9bf78dfdfe146a04057f82df9e2402d2a975939", size = 784426 }, + { url = "https://files.pythonhosted.org/packages/ca/f5/1ef9e2a83a5947c57ebff0b377cb5727c3d5ec1992317a320d035cd0dbb6/regex-2026.7.19-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c4585c3e64b4f9e583b4d2683f18f5d5d872b3d71dcf24594b74ecc23602fa96", size = 860600 }, + { url = "https://files.pythonhosted.org/packages/a0/02/073af33a3ec149241d11c80acea91e722aa0adbf05addd50f251c4fe89c3/regex-2026.7.19-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:571fde9741eb0ccde23dd4e0c1d50fbae910e901fa7e629faf39b2dda740d220", size = 765950 }, + { url = "https://files.pythonhosted.org/packages/81/a9/d1e9f819dc394a568ef370cd56cf25394e957a2235f8370f23b576e5a475/regex-2026.7.19-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:15b364b9b98d6d2fe1a85034c23a3180ff913f46caddc3895f6fd65186255ccc", size = 851794 }, + { url = "https://files.pythonhosted.org/packages/03/3a/8ae83eda7579feacdf984e71fb9e70635fb6f832eeddca58427ec4fca926/regex-2026.7.19-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffd8893ccc1c2fce6e0d6ca402d716fe1b29db70c7132609a05955e31b2aa8f2", size = 789845 }, + { url = "https://files.pythonhosted.org/packages/4b/23/c195cbfe5a75fdec64d8f6554fd15237b837919d2c61bdc141d7c807b08b/regex-2026.7.19-cp313-cp313-win32.whl", hash = "sha256:f0fa4fa9c3632d708742baf2282f2055c11d888a790362670a403cbf48a2c404", size = 267135 }, + { url = "https://files.pythonhosted.org/packages/b2/80/a11de8404b7272b70acb45c1c05987cce60b45d5693da2e176f0e390d564/regex-2026.7.19-cp313-cp313-win_amd64.whl", hash = "sha256:d51ffd3427640fa2da6ade574ceba932f210ad095f65fcc450a2b0a0d454868e", size = 277747 }, + { url = "https://files.pythonhosted.org/packages/d1/29/0f5c8eff1b4f1f3d83276d365fccecf666afcc7d947420943bf394d07adb/regex-2026.7.19-cp313-cp313-win_arm64.whl", hash = "sha256:c670fe7be5b6020b76bc6e8d2196074657e1327595bca93a389e1a76ab130ad8", size = 277129 }, + { url = "https://files.pythonhosted.org/packages/dc/4c/44b74742052cedda40f9ae469532a037112f7311a36669a891fba8984bb0/regex-2026.7.19-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db47b561c9afd884baa1f96f797c9ca369872c4b65912bc691cfa99e68340af2", size = 501134 }, + { url = "https://files.pythonhosted.org/packages/f0/45/bbd038b5e39ee5613a5a689290145b40058cc152c41de9cc23639d2b9734/regex-2026.7.19-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65dcd28d3eba2ab7c2fd906485cc301392b47cc2234790d27d4e4814e02cdfda", size = 299418 }, + { url = "https://files.pythonhosted.org/packages/65/38/c5bde94b4cedfd5850d64c3f08222d8e1600e84f6ee71d9b44b4b8163f74/regex-2026.7.19-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f2e7f8e2ab6c2922be02c7ec45185aa5bd771e2e57b95455ee343a44d8130dff", size = 294486 }, + { url = "https://files.pythonhosted.org/packages/d7/6a/2f5e107cb26c960b781967178899daf2787a7ab151844ed3c01d6fc95474/regex-2026.7.19-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe31f28c94402043161876a258a9c6f757cb485905c7614ce8d6cd40e6b7bdc1", size = 811643 }, + { url = "https://files.pythonhosted.org/packages/37/d4/a2f963406d7d73a62eed84ba05a258afb6cad1b21aa4517443ce40506b78/regex-2026.7.19-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8f6fa298bb4f7f58a33334406218ba74716e68feddf5e4e54cd5d8082705abf", size = 871081 }, + { url = "https://files.pythonhosted.org/packages/45/a3/44be546340bedb15f13063f5e7fe16793ea4d9ea2e805d09bd174ac27724/regex-2026.7.19-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cc1b2440423a851fad781309dd87843868f4f66a6bcd1ddb9225cf4ec2c84732", size = 917372 }, + { url = "https://files.pythonhosted.org/packages/f8/f6/e0870b0fd2a40dba0074e4b76e514b21313d37946c9248453e34ec43923e/regex-2026.7.19-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ac59a0900474a52b7c04af8196affc22bd9842acb0950df12f7b813e983609a", size = 816089 }, + { url = "https://files.pythonhosted.org/packages/ae/27/957e8e22690ad6634572b39b71f130a6105f4d0718bb16849eac00fff147/regex-2026.7.19-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4896db1f4ce0576765b8272aa922df324e0f5b9bb2c3d03044ff32a7234a9aba", size = 785206 }, + { url = "https://files.pythonhosted.org/packages/76/a4/186e410941e731037c01166069ab86da9f65e8f8110c18009ccf4bd623ee/regex-2026.7.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4e6883a021db30511d9fb8cfb0f222ce1f2c369f7d4d8b0448f449a93ba0bdfc", size = 800431 }, + { url = "https://files.pythonhosted.org/packages/73/9f/e4e10e023d291d64a33e246610b724493bf1ce98e0e59c9b7c837e5acfb7/regex-2026.7.19-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:09523a592938aa9f587fb74467c63ff0cf88fc3df14c82ab0f0517dcf76aaa62", size = 864906 }, + { url = "https://files.pythonhosted.org/packages/24/57/ccb20b6be5f1f52a053d1ba2a8f7a077edb9d918248b8490d7506c6832b3/regex-2026.7.19-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1ebac3474b8589fce2f9b225b650afd61448f7c73a5d0255a10cc6366471aed1", size = 773559 }, + { url = "https://files.pythonhosted.org/packages/a3/82/f3b263cf8fad927dc102891da8502e718b7ff9d19af7a2a07c03865d7188/regex-2026.7.19-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4a0530bb1b8c1c985e7e2122e2b4d3aedd8a3c21c6bfddae6767c4405668b56e", size = 857739 }, + { url = "https://files.pythonhosted.org/packages/47/2e/1687bd1b6c2aed5e672ccf845fc11557821fe7366d921b50889ea5ce57bf/regex-2026.7.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef7eeb108c47ce7bcc9513e51bcb1bf57e8f483d52fce68a8642e3527141ae0", size = 804522 }, + { url = "https://files.pythonhosted.org/packages/76/7c/cc4e7655181b2d9235b704f2c5e19d8eff002bbc437bae59baee0e381aca/regex-2026.7.19-cp313-cp313t-win32.whl", hash = "sha256:64b6ca7391a1395c2638dd5c7456d67bea44fc6c5e8e92c5dc8aa6a8f23292b4", size = 269141 }, + { url = "https://files.pythonhosted.org/packages/bb/14/961b4c7b05a2391c32dbc85e27773076671ef8f97f36cec70fe414734c02/regex-2026.7.19-cp313-cp313t-win_amd64.whl", hash = "sha256:f04b9f56b0e0614c0126be12c2c2d9f8850c1e57af302bd0a63bed379d4af974", size = 280036 }, + { url = "https://files.pythonhosted.org/packages/ce/67/795644550d788ddbb6dc458c95895f8009978ea6d6ea76b005eb3f45e8c9/regex-2026.7.19-cp313-cp313t-win_arm64.whl", hash = "sha256:fcee38cd8e5089d6d4f048ba1233b3ad76e5954f545382180889112ff5cb712d", size = 279394 }, + { url = "https://files.pythonhosted.org/packages/d2/25/0c4c452f8ef3efe456745b2f33195f5904b573fb4c2ff3f0cb9ec188461e/regex-2026.7.19-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a81758ed242b861b72e778ba34d41366441a2e10b16b472784c88da2dea7e2dd", size = 496750 }, + { url = "https://files.pythonhosted.org/packages/24/9e/b70ca6c1704f6c7cd32a9e143c86cc5968d10981eca284bad670c245ea7d/regex-2026.7.19-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4aa5435cdb3eb6f55fe98a171b05e3fbcd95fadaa4aa32acf62afd9b0cfdbcac", size = 297093 }, + { url = "https://files.pythonhosted.org/packages/87/74/0b692da2520d51fbff19c88b83d97e4c702909dd02386c585998b7e2dbed/regex-2026.7.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:60be8693a1dadc210bbcbc0db3e26da5f7d01d1d5a3da594e99b4fa42df404f5", size = 292043 }, + { url = "https://files.pythonhosted.org/packages/e3/a7/1d478e614016045a33feae57446215f9fd65b665a5ceb2f891fb3183bc52/regex-2026.7.19-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19662dbedbe783d323196312d38f5ba53cf56296378252171985da6899887d3", size = 797214 }, + { url = "https://files.pythonhosted.org/packages/aa/ae/11b9c9411d92c30e3d2db32df5a31133e4a99a8fc397a604fd08f6c4bffb/regex-2026.7.19-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d15df07081d91b76ff20d43f94592ee110330152d617b730fdbe5ef9fb680053", size = 866433 }, + { url = "https://files.pythonhosted.org/packages/b1/62/2b2efc4992f91d6d204b24c647c9f9412e85379d92b7c0ab9fdae622327e/regex-2026.7.19-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:56ad4d9f77df871a99e25c37091052a02528ec0eb059de928ee33956b854b45b", size = 911360 }, + { url = "https://files.pythonhosted.org/packages/14/71/986ceea9aa3da548bf1357cad89b63915ec6d21ec957c8113b29ece567df/regex-2026.7.19-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7322ec6cc9fba9d49ab888bb82d67ac5625627aa168f0165139b17018df3fb8a", size = 801275 }, + { url = "https://files.pythonhosted.org/packages/15/be/ce9d9534b2cda96eab32c548261224b9b4e220a4126f098f60f42ae7b4cd/regex-2026.7.19-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c7472192ebfad53a6be7c4a8bfb2d64b81c0e93a1fc8c57e1dd0b638297b5d1", size = 777131 }, + { url = "https://files.pythonhosted.org/packages/61/2b/58b5c710f2c3929515a25f3a1ca0dad0dcd4518d4fff3cf23bc7adb8dcd2/regex-2026.7.19-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c10b82c2634df08dfb13b1f04e38fe310d086ee092f4f69c0c8da234251e556e", size = 785020 }, + { url = "https://files.pythonhosted.org/packages/84/03/5fe091935b74f15fe0f97998c215cae418d1c0413f6258c7d4d2e83aa37f/regex-2026.7.19-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:17ed5692f6acc4183e98331101a5f9e4f64d72fe58b753da4d444a2c77d05b12", size = 861263 }, + { url = "https://files.pythonhosted.org/packages/d8/fa/d60bf82e10841eef62a9e32aac401468f05fddfbcb2942e342b1ba3d2433/regex-2026.7.19-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:22a992de9a0d91bda927bf02b94351d737a0302905432c88a53de7c4b9ce62e2", size = 766199 }, + { url = "https://files.pythonhosted.org/packages/bf/5d/11e64d151b0662b81d6bf644c74dc118d461df85bdf2577fadbbf751788a/regex-2026.7.19-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:618a0aed532be87294c4477b0481f3aa0f1520f4014a4374dd4cf789b4cd2c97", size = 851317 }, + { url = "https://files.pythonhosted.org/packages/7c/34/532efb87488d90807bae6a443d357ee5e2728a478c597619c8aaa17cc0bd/regex-2026.7.19-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ce9e679f776649746729b6c86382da519ef649c8e34cc41df0d2e5e0f6c36d4", size = 789557 }, + { url = "https://files.pythonhosted.org/packages/d6/90/3a8d5ca977171ec3ae21a71207d2228b2663bde14d7f7ef0e6363ecf9290/regex-2026.7.19-cp314-cp314-win32.whl", hash = "sha256:73f272fba87b8ccfe70a137d02a54af386f6d27aa509fbffdd978f5947aae1aa", size = 272531 }, + { url = "https://files.pythonhosted.org/packages/96/e1/8862885e70409de70e8c005f57fb2e7be8d9ef0317250d60f4c9660a300d/regex-2026.7.19-cp314-cp314-win_amd64.whl", hash = "sha256:d721e53758b2cca74990185eb0671dd466d7a388a1a45d0c6f4c13cef41a68ac", size = 280831 }, + { url = "https://files.pythonhosted.org/packages/08/82/2693e53e29f9104d9de95d37ce4dd826bd32d5f9c0085d3aa6ac042675c4/regex-2026.7.19-cp314-cp314-win_arm64.whl", hash = "sha256:65fa6cb38ed5e9c3637e68e544f598b39c3b86b808ed0627a67b68320384b459", size = 281099 }, + { url = "https://files.pythonhosted.org/packages/92/b7/9a01aa16461a18cde9d7b9c3ab21e501db2ce33725f53014342b91df2b0a/regex-2026.7.19-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:5a2721c8720e2cb3c209925dfb9200199b4b07361c9e01d321719404b21458b3", size = 501121 }, + { url = "https://files.pythonhosted.org/packages/f3/5e/bbaeca815dc9191c424c94a4fdc5c87c75748a64a6271821212ebdd4e1a3/regex-2026.7.19-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:199535629f25caf89698039af3d1ad5fcae7f933e2112c73f1cdf49165c99518", size = 299415 }, + { url = "https://files.pythonhosted.org/packages/cd/d6/0dd1a321afaab95eb7ff44aa0f637301786f1dc71c6b797b9ed236ed8890/regex-2026.7.19-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9b60d7814174f059e5de4ab98271cc5ba9259cfea55273a81544dceea32dc8d9", size = 294483 }, + { url = "https://files.pythonhosted.org/packages/92/5f/40bacf91d0904f812e13bbbab3864604c463eced8afdc54aeaa50492ea95/regex-2026.7.19-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbece16025afda5e3031af0c4059207e61dcf73ef13af844964f57f387d1c435", size = 811833 }, + { url = "https://files.pythonhosted.org/packages/94/7c/4902744261f775aeede8b5627314b38482da29cf49a57b66a6fb753246c5/regex-2026.7.19-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d24ecb4f5e009ea0bd275ee37ad9953b32005e2e5e60f8bbae16da0dbbf0d3a0", size = 871270 }, + { url = "https://files.pythonhosted.org/packages/16/70/6980c9be6bf21c0a60ed3e0aea39cf419ecf3b08d1d9947bc56e196ef186/regex-2026.7.19-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8cae6fd77a5b72dae505084b1a2ee0360139faf72fedbab667cd7cc65aae7a6a", size = 917534 }, + { url = "https://files.pythonhosted.org/packages/52/92/8b2bd872782ce8c42691e39acb38eb8efe014e5ddb78ad7d943d6f197ce9/regex-2026.7.19-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9724e6cb5e478cd7d8cabf027826178739cb18cf0e117d0e32814d479fa02276", size = 816135 }, + { url = "https://files.pythonhosted.org/packages/de/2d/33a602f657bdc4041f17d79f92ab18261d255d91a06117a6e29df023e5e2/regex-2026.7.19-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:572fc57b0009c735ee56c175ea021b637a15551a312f56734277f923d6fd0f6c", size = 785492 }, + { url = "https://files.pythonhosted.org/packages/9e/36/0987cf4cb271680064a70d24a475873775a151d0b7058698a006cb0cae4a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:20568e182eb82d39a6bf7cff3fd58566f14c75c6f74b2c8c96537eecf9010e3a", size = 800658 }, + { url = "https://files.pythonhosted.org/packages/a8/24/c14f31c135e1ba55fa4f9a58ca98d0842512bf6188230763c31c8f449e3b/regex-2026.7.19-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1d58561843f0ff7dc78b4c28b5e2dc388f3eff94ebc8a232a3adba961fc00009", size = 865073 }, + { url = "https://files.pythonhosted.org/packages/14/85/181a12211f22469f24d2de1ebddfe397d2396e2c29013b9a58134a91069a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:61bb1bd45520aacd56dd80943bd34991fb5350afdd1f36f2282230fd5154a218", size = 773684 }, + { url = "https://files.pythonhosted.org/packages/23/58/bd1a0c1a62251366f8d21f41b1ea3c76994962071b8b6ea42f72d505c0f0/regex-2026.7.19-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:cd3584591ea4429026cdb931b054342c2bcf189b44ff367f8d5c15bc092a2966", size = 857769 }, + { url = "https://files.pythonhosted.org/packages/e4/4f/f7e2dad6756b2fe1fe75dd90a628c3b45f249d39f948dd90cd2476325417/regex-2026.7.19-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cc26a66e212fa5d6c6170c3a40d99d888db3020c6fdab1523250d4341382e44", size = 804546 }, + { url = "https://files.pythonhosted.org/packages/2b/d7/01d31d5bdb09bc026fab77f59a371fdf8f9b292e4810546c56182ca70498/regex-2026.7.19-cp314-cp314t-win32.whl", hash = "sha256:2c4e61e2e1be56f63ec3cc618aa9e0de81ef6f43d177205451840022e24f5b78", size = 274526 }, + { url = "https://files.pythonhosted.org/packages/52/0e/cea4ce73bc0a8247a0748228ae6669984c7e1f8134b6fa66e59c0572e0ea/regex-2026.7.19-cp314-cp314t-win_amd64.whl", hash = "sha256:c639ea314df70a7b2811e8020448c75af8c9445f5a60f8a4ced81c306a9380c2", size = 283763 }, + { url = "https://files.pythonhosted.org/packages/6f/b6/26e41975febae63b7a6e3e02f32cff6cff2e4f10d19c929082f56aebf7c6/regex-2026.7.19-cp314-cp314t-win_arm64.whl", hash = "sha256:9a15e785f244f3e07847b984ce8773fc3da10a9f3c131cc49a4c5b4d672b4547", size = 283451 }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "charset-normalizer", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "idna", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "urllib3", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075 }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pygments", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654 }, +] + +[[package]] +name = "rpds-py" +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174 }, + { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513 }, + { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783 }, + { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316 }, + { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423 }, + { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077 }, + { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315 }, + { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502 }, + { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673 }, + { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964 }, + { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446 }, + { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975 }, + { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453 }, + { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219 }, + { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137 }, + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691 }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542 }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180 }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067 }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509 }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754 }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189 }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750 }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576 }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807 }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187 }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030 }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185 }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394 }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753 }, + { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012 }, + { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203 }, + { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984 }, + { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815 }, + { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545 }, + { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828 }, + { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678 }, + { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811 }, + { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382 }, + { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832 }, + { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011 }, + { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431 }, + { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710 }, + { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454 }, + { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063 }, + { url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510 }, + { url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495 }, + { url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454 }, + { url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583 }, + { url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919 }, + { url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725 }, + { url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255 }, + { url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060 }, + { url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960 }, + { url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356 }, + { url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319 }, + { url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508 }, + { url = "https://files.pythonhosted.org/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", size = 202504 }, + { url = "https://files.pythonhosted.org/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", size = 220380 }, + { url = "https://files.pythonhosted.org/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", size = 215976 }, + { url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840 }, + { url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282 }, + { url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403 }, + { url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055 }, + { url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419 }, + { url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848 }, + { url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369 }, + { url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673 }, + { url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500 }, + { url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978 }, + { url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350 }, + { url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486 }, + { url = "https://files.pythonhosted.org/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", size = 201068 }, + { url = "https://files.pythonhosted.org/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", size = 220600 }, + { url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726 }, + { url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587 }, + { url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585 }, + { url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479 }, + { url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418 }, + { url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123 }, + { url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351 }, + { url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827 }, + { url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966 }, + { url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680 }, + { url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853 }, + { url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715 }, + { url = "https://files.pythonhosted.org/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", size = 202864 }, + { url = "https://files.pythonhosted.org/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", size = 220430 }, + { url = "https://files.pythonhosted.org/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", size = 215877 }, + { url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933 }, + { url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274 }, + { url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763 }, + { url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467 }, + { url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689 }, + { url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340 }, + { url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179 }, + { url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993 }, + { url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909 }, + { url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584 }, + { url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357 }, + { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533 }, + { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204 }, + { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719 }, + { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791 }, + { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842 }, + { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094 }, + { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662 }, + { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896 }, + { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545 }, + { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059 }, + { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235 }, + { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008 }, + { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118 }, + { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138 }, + { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486 }, +] + +[[package]] +name = "rtree" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/09/7302695875a019514de9a5dd17b8320e7a19d6e7bc8f85dcfb79a4ce2da3/rtree-1.4.1.tar.gz", hash = "sha256:c6b1b3550881e57ebe530cc6cffefc87cd9bf49c30b37b894065a9f810875e46", size = 52425 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/d9/108cd989a4c0954e60b3cdc86fd2826407702b5375f6dfdab2802e5fed98/rtree-1.4.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d672184298527522d4914d8ae53bf76982b86ca420b0acde9298a7a87d81d4a4", size = 468484 }, + { url = "https://files.pythonhosted.org/packages/f3/cf/2710b6fd6b07ea0aef317b29f335790ba6adf06a28ac236078ed9bd8a91d/rtree-1.4.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7e48d805e12011c2cf739a29d6a60ae852fb1de9fc84220bbcef67e6e595d7d", size = 436325 }, + { url = "https://files.pythonhosted.org/packages/55/e1/4d075268a46e68db3cac51846eb6a3ab96ed481c585c5a1ad411b3c23aad/rtree-1.4.1-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa8c4496e31e9ad58ff6c7df89abceac7022d906cb64a3e18e4fceae6b77f65", size = 459789 }, + { url = "https://files.pythonhosted.org/packages/d1/75/e5d44be90525cd28503e7f836d077ae6663ec0687a13ba7810b4114b3668/rtree-1.4.1-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12de4578f1b3381a93a655846900be4e3d5f4cd5e306b8b00aa77c1121dc7e8c", size = 507644 }, + { url = "https://files.pythonhosted.org/packages/fd/85/b8684f769a142163b52859a38a486493b05bafb4f2fb71d4f945de28ebf9/rtree-1.4.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b558edda52eca3e6d1ee629042192c65e6b7f2c150d6d6cd207ce82f85be3967", size = 1454478 }, + { url = "https://files.pythonhosted.org/packages/e9/a4/c2292b95246b9165cc43a0c3757e80995d58bc9b43da5cb47ad6e3535213/rtree-1.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f155bc8d6bac9dcd383481dee8c130947a4866db1d16cb6dff442329a038a0dc", size = 1555140 }, + { url = "https://files.pythonhosted.org/packages/74/25/5282c8270bfcd620d3e73beb35b40ac4ab00f0a898d98ebeb41ef0989ec8/rtree-1.4.1-py3-none-win_amd64.whl", hash = "sha256:efe125f416fd27150197ab8521158662943a40f87acab8028a1aac4ad667a489", size = 389358 }, + { url = "https://files.pythonhosted.org/packages/3f/50/0a9e7e7afe7339bd5e36911f0ceb15fed51945836ed803ae5afd661057fd/rtree-1.4.1-py3-none-win_arm64.whl", hash = "sha256:3d46f55729b28138e897ffef32f7ce93ac335cb67f9120125ad3742a220800f0", size = 355253 }, +] + +[[package]] +name = "s3transfer" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/43/35e4d8aa320bffe8287fe8f65f578fa2d2db0a64212f0e710dce58267854/s3transfer-0.19.2.tar.gz", hash = "sha256:ba0309fd86be3c27dbf78cdd813c13c5e1df16e5874b99d2535ebbdfb9892993", size = 165592 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/e7/5c595c75e9f41a44f30e526eda465ea0b4eec93470e074e4a111b253f13a/s3transfer-0.19.2-py3-none-any.whl", hash = "sha256:d8168eccca828cbb2cd573675333f3bddd254313a9c42494b84c76b539e8ba25", size = 90216 }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568 }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562 }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844 }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823 }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461 }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148 }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040 }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832 }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930 }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670 }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679 }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683 }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361 }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401 }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540 }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500 }, +] + +[package.optional-dependencies] +torch = [ + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "torch", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675 }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057 }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032 }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533 }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057 }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300 }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333 }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314 }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512 }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248 }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954 }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662 }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366 }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017 }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842 }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890 }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557 }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856 }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682 }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340 }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199 }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001 }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719 }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429 }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952 }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063 }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449 }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943 }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621 }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708 }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135 }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977 }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601 }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667 }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159 }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771 }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910 }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980 }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543 }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510 }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131 }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032 }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766 }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007 }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333 }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066 }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763 }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984 }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877 }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750 }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858 }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723 }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098 }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397 }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163 }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291 }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317 }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327 }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165 }, +] + +[[package]] +name = "semchunk" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpire", extra = ["dill"], marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tqdm", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/a0/ce7e3d6cc76498fd594e667d10a03f17d7cced129e46869daec23523bf5a/semchunk-3.2.5.tar.gz", hash = "sha256:ee15e9a06a69a411937dd8fcf0a25d7ef389c5195863140436872a02c95b0218", size = 17667 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/95/12d226ee4d207cb1f77a216baa7e1a8bae2639733c140abe8d0316d23a18/semchunk-3.2.5-py3-none-any.whl", hash = "sha256:fd09cc5f380bd010b8ca773bd81893f7eaf11d37dd8362a83d46cedaf5dae076", size = 13048 }, +] + +[[package]] +name = "setuptools" +version = "84.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216 }, +] + +[[package]] +name = "shapely" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038 }, + { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039 }, + { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519 }, + { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842 }, + { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316 }, + { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586 }, + { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961 }, + { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856 }, + { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550 }, + { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556 }, + { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308 }, + { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844 }, + { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842 }, + { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714 }, + { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745 }, + { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861 }, + { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644 }, + { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887 }, + { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931 }, + { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855 }, + { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960 }, + { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851 }, + { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890 }, + { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151 }, + { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130 }, + { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802 }, + { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460 }, + { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223 }, + { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760 }, + { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078 }, + { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178 }, + { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756 }, + { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290 }, + { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463 }, + { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145 }, + { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806 }, + { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803 }, + { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301 }, + { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247 }, + { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019 }, + { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137 }, + { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884 }, + { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320 }, + { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931 }, + { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406 }, + { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511 }, + { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607 }, + { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682 }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "soupsieve" +version = "2.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/99/a6ca3beb3ccacb41fb3321d8a60e5566f9e6467601ef8eba6a17e1b89778/soupsieve-2.9.2.tar.gz", hash = "sha256:4a55d8cf158a9c2e587fa4922f1bbb91d68ac829e2d6f25403a85747c71daf74", size = 122445 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/dc/ad025c1ee131eba60c69f4dd5779b18fcf1e6b21a343e2162a84d5d133c7/soupsieve-2.9.2-py3-none-any.whl", hash = "sha256:8089a26fd974ca7a1f30276d3d8492ab266ab15af581642dfe8aa162e0c1c823", size = 37370 }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.15' and platform_machine == 'AMD64') or (python_full_version < '3.15' and platform_machine == 'WIN32') or (python_full_version < '3.15' and platform_machine == 'aarch64') or (python_full_version < '3.15' and platform_machine == 'amd64') or (python_full_version < '3.15' and platform_machine == 'ppc64le') or (python_full_version < '3.15' and platform_machine == 'win32') or (python_full_version < '3.15' and platform_machine == 'x86_64') or (platform_machine == 'AMD64' and platform_system != 'Linux') or (platform_machine == 'WIN32' and platform_system != 'Linux') or (platform_machine == 'aarch64' and platform_system != 'Linux') or (platform_machine == 'amd64' and platform_system != 'Linux') or (platform_machine == 'ppc64le' and platform_system != 'Linux') or (platform_machine == 'win32' and platform_system != 'Linux') or (platform_machine == 'x86_64' and platform_system != 'Linux') or (platform_machine == 'AMD64' and sys_platform == 'darwin') or (platform_machine == 'WIN32' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'darwin') or (platform_machine == 'amd64' and sys_platform == 'darwin') or (platform_machine == 'ppc64le' and sys_platform == 'darwin') or (platform_machine == 'win32' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'AMD64' and sys_platform == 'emscripten') or (platform_machine == 'WIN32' and sys_platform == 'emscripten') or (platform_machine == 'aarch64' and sys_platform == 'emscripten') or (platform_machine == 'amd64' and sys_platform == 'emscripten') or (platform_machine == 'ppc64le' and sys_platform == 'emscripten') or (platform_machine == 'win32' and sys_platform == 'emscripten') or (platform_machine == 'x86_64' and sys_platform == 'emscripten')" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/21/77b4c147963073040dc3c3a5cb7a8c3001a1893c0209432cb77f9df836aa/sqlalchemy-2.0.52.tar.gz", hash = "sha256:5e2d46356ac2ccb7d268ab6c2319ac6a2b42f1b8d5fd8bd3d46855cd82abee97", size = 9945637 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/08/cc5f7627b92f1456bc0b5fb7e98af4600248abe422a44da0d17a3fe6a448/sqlalchemy-2.0.52-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0c3ce43907374889f3352bdcc6195c970148a2cb71574cd0237a5071a37fb6c", size = 2172460 }, + { url = "https://files.pythonhosted.org/packages/ed/dc/9a2abad8bfc8fdcd38c64adc056aeefab7aaa96ecd32f5e8c140e6375f17/sqlalchemy-2.0.52-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a0d48c4b80717c61385b4e966e087c839a66cfd7b780641dcb428f4dba65608", size = 3355720 }, + { url = "https://files.pythonhosted.org/packages/a8/73/e75597b5841043e3c74055d00d4feb53d9a49a5c89ba2450d2d9aab53597/sqlalchemy-2.0.52-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938325a5373267afc53bfbe72983b20fbd64ca47842aac62433c3da1137ecff1", size = 3354394 }, + { url = "https://files.pythonhosted.org/packages/12/25/410fbc6c2f1fa8310f4ef1b6847d47d0ac1c042c7b4e81eaaca063d030a9/sqlalchemy-2.0.52-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8438a98d49424acf69d0d53c0a522951dfe49a6f2d86417fbb37ad3066ab43", size = 3306991 }, + { url = "https://files.pythonhosted.org/packages/b2/ba/25ffd5c24681ea4b46e62c80ceca8200ce204de1773366321306cf3f608a/sqlalchemy-2.0.52-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4699dbb8d396d199e7e78fd4d525e3ad3d6008a9c8c0160b87e74c606c2c3736", size = 3327454 }, + { url = "https://files.pythonhosted.org/packages/c2/f1/0f1b1d4800e51218e736a06ed55a3b2a59c257600bbaca7673bf13d2dbec/sqlalchemy-2.0.52-cp311-cp311-win32.whl", hash = "sha256:cef328349452ae152637df4d11ce5a0919ecdf0a363e16c830c3518ee33bde72", size = 2131248 }, + { url = "https://files.pythonhosted.org/packages/7a/f0/04d2ac5ad66f3d31278f37064ed5f5ef3fe653f7bdaa67036663f223d186/sqlalchemy-2.0.52-cp311-cp311-win_amd64.whl", hash = "sha256:f1c850792a3b25a3ad74dade3f05e4f402cdebfea27438bcadafaa1617f77bcc", size = 2156943 }, + { url = "https://files.pythonhosted.org/packages/e0/d5/1b77a026d161f98a08f11af1a5f6c47b98ee7c7e2648af525a1004826c78/sqlalchemy-2.0.52-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be8c49131665dfe2cc74c498aa1240ffb548d0fd901325dd11c2c7a18956f727", size = 2170940 }, + { url = "https://files.pythonhosted.org/packages/54/bd/f444444adb37b5d53753fb1730ee7a421628e2e3b756c4da461af7e6394a/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b2d9e507a458832adcfbd8af6e2036ddf069b7710b799448542ebccae2dceee", size = 3383415 }, + { url = "https://files.pythonhosted.org/packages/be/57/2eadf93a552568c57e8680b7e58bb5e9770d80942a1bdbaf4f2f63f0d7c8/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8738008376d22f30f411ea3efecf39b51110b6996d80bb73786f30bcfdd5fd3b", size = 3398577 }, + { url = "https://files.pythonhosted.org/packages/15/c3/2887cf9dd111d1fbf05d22165b404c221ef43e029f7a2695e7302f27a7cc/sqlalchemy-2.0.52-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37a4d548327b6cab9c7d8cdb4e0e82feabee0110c4d150059068e2d1cfbd99ee", size = 3328225 }, + { url = "https://files.pythonhosted.org/packages/02/0f/466bdf9e1feeeef5587f868c187d8687e21ff8c85b1775e9041130181132/sqlalchemy-2.0.52-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e49f51a5d59857a7a0dcaf9469febf7197d9394bd88f00d69c2c4e848112cdbf", size = 3357374 }, + { url = "https://files.pythonhosted.org/packages/22/20/5c2b4583904af4173076dda1c9e53c9e2ffc7a702d2efde0216bbacbf7cb/sqlalchemy-2.0.52-cp312-cp312-win32.whl", hash = "sha256:afda3ec521d0517d0de783fc70030775841900896d832de5bbd066549290470e", size = 2129366 }, + { url = "https://files.pythonhosted.org/packages/ed/06/543dab8ef62d4e9fb96fb31a30c2b8b14a8763bccf48d428294d6b3041c0/sqlalchemy-2.0.52-cp312-cp312-win_amd64.whl", hash = "sha256:2d5e53e36e37129fe0be8b9d08b6e4052c10a963ee6cda56c8c10dcc194b99ca", size = 2157344 }, + { url = "https://files.pythonhosted.org/packages/7f/18/e30c6fe1eca1bf34a39fbdd6066121cc9974c850faf6f349eac563697a26/sqlalchemy-2.0.52-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2eb3c6a64b1bfe6704777cfd504e7b8ad093a5f3e03ce67663a5e6742f294e43", size = 2167724 }, + { url = "https://files.pythonhosted.org/packages/d0/56/2e17d161a4f7ecc1c2ffb93e607b4e1898bb551b451b283235acb8f6ce47/sqlalchemy-2.0.52-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:923bb183c1dc64fdf7b717965e3d59938ec4f8b8710b419a21ce403e5da9a9e1", size = 3321189 }, + { url = "https://files.pythonhosted.org/packages/cf/b8/8490916e893f3f8d74dc9cc54c078619364999dee37047a188e73abbc852/sqlalchemy-2.0.52-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:651d6d8782e80679e6151707c7b490834d46ada526328895abf567f25e63d29c", size = 3338185 }, + { url = "https://files.pythonhosted.org/packages/8b/f7/752cc8ee453da222829b3f5c4613614bf750d97429363b70414fa10478e4/sqlalchemy-2.0.52-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b08cddb8989775e3c88799d86704bdfc3ee6e9846118201aa5997f16f27e3a15", size = 3271698 }, + { url = "https://files.pythonhosted.org/packages/51/e6/074ade0c07b9e4c8e8bca46820320ed94df9702afdb6f2af06623068d2e6/sqlalchemy-2.0.52-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab66fa9618269390d4dfa222f2f2f88f7bc4bf5da13905131b818217db7e8057", size = 3308936 }, + { url = "https://files.pythonhosted.org/packages/66/07/557c0d04716705599227945ac14e0a17ad0338e899f37d8c2ddff4dcc663/sqlalchemy-2.0.52-cp313-cp313-win32.whl", hash = "sha256:c63bda077685c85ca513286547a531ba57e7a68cf0a7ed3bafcc2bbd18896f4d", size = 2127308 }, + { url = "https://files.pythonhosted.org/packages/96/4e/226eda27654318ce525d043025221f689abef883da2c7126f9065121618c/sqlalchemy-2.0.52-cp313-cp313-win_amd64.whl", hash = "sha256:9876b09b9f1ce7398b0ffece585c0a911244c53191187341f6bcae640e133751", size = 2153876 }, + { url = "https://files.pythonhosted.org/packages/d5/f5/71cb30af58c9b80a4e1fac0b73bb48f86d497a774a6a2eb6d2f1e657bb73/sqlalchemy-2.0.52-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:410d52be41d17f1a236d19520fbe776257dc16516ed06bd16d433311842aefd9", size = 2169537 }, + { url = "https://files.pythonhosted.org/packages/4c/93/d07ebd645d1b07b6b5ed63450a70f063a346a7e0f2c8810daf2e532400cb/sqlalchemy-2.0.52-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfe9ce533dbe4d0a2ae1486546619bd30b76bcd670539a44d910361376175f5e", size = 3319606 }, + { url = "https://files.pythonhosted.org/packages/ae/5c/290c84c7c2566ecd3b65baaae0fddec9bc33b033b398a06123bb86fbfc6e/sqlalchemy-2.0.52-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:812bae5138bfc0aa46fb0686da0fc7f581f68e2bbb05bc24c3713bebaedd1437", size = 3323642 }, + { url = "https://files.pythonhosted.org/packages/13/f5/2cc160590ca49173359557880b92a0572293ccb899e8f6cedf150c5a3ddf/sqlalchemy-2.0.52-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:50bff43b632a56fbf5ed9afdd76307e1512b62051bcd5afb341ae67205bbb6c8", size = 3268125 }, + { url = "https://files.pythonhosted.org/packages/35/f3/ea8933fc9f7d1353e9c2ff9965eae687c4cef181120574591ed2fa0633e1/sqlalchemy-2.0.52-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:49565daf5af554f538e23aef1fc81a95a4e49658f152285e45c02f5fc44f04cd", size = 3289516 }, + { url = "https://files.pythonhosted.org/packages/45/67/05cf86541c1e1716fca1e4a996954a439cd74501707cda607fb7cb02ef50/sqlalchemy-2.0.52-cp314-cp314-win32.whl", hash = "sha256:ab9da41e61b9979b910499d633b241df20c51ee5037e5405b11c2faac3cbe1a2", size = 2130249 }, + { url = "https://files.pythonhosted.org/packages/96/d7/8ac6ffa1e36169e762ef65bd835046abb2251b1bc17f8f6708e14ed8d31f/sqlalchemy-2.0.52-cp314-cp314-win_amd64.whl", hash = "sha256:a593db51b3bae75db17a5738ad5f992244b3a03863f83c28117ee482c6a3f76d", size = 2156718 }, + { url = "https://files.pythonhosted.org/packages/dc/4b/e01a737eef378e734cc6394a82248a6ce13b167dfa36c731075ce9fc9c64/sqlalchemy-2.0.52-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1e61d08bdf4ee2f41024569e3400de7d6734ba498144766b11260936ccfa582", size = 2190344 }, + { url = "https://files.pythonhosted.org/packages/b3/3f/3582293d1e185e71d19d7c731c3e2ee20ba21981c4a1115c0806c1f62120/sqlalchemy-2.0.52-py3-none-any.whl", hash = "sha256:3b81b8363a919ce53453591cdb93702e6bd54ade6c4fa2f468fc053baee5ed89", size = 1950700 }, +] + +[[package]] +name = "starlette" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969 }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 }, +] + +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814 }, +] + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275 }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472 }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736 }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835 }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673 }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818 }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195 }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982 }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245 }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069 }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263 }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429 }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363 }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786 }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133 }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704 }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454 }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561 }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824 }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227 }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859 }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204 }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084 }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285 }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924 }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018 }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948 }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341 }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159 }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290 }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141 }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847 }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088 }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866 }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887 }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704 }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628 }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180 }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674 }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976 }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755 }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265 }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726 }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859 }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713 }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084 }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973 }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223 }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973 }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082 }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490 }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263 }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736 }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717 }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461 }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855 }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144 }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683 }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196 }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393 }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583 }, +] + +[[package]] +name = "torch" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-bindings", marker = "python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "(python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin') or (platform_system == 'Linux' and sys_platform == 'emscripten')" }, + { name = "filelock", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "fsspec", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "jinja2", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "networkx", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "nvidia-cudnn-cu13", marker = "(python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin') or (platform_system == 'Linux' and sys_platform == 'emscripten')" }, + { name = "nvidia-cusparselt-cu13", marker = "(python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin') or (platform_system == 'Linux' and sys_platform == 'emscripten')" }, + { name = "nvidia-nccl-cu13", marker = "(python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin') or (platform_system == 'Linux' and sys_platform == 'emscripten')" }, + { name = "nvidia-nvshmem-cu13", marker = "(python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin') or (platform_system == 'Linux' and sys_platform == 'emscripten')" }, + { name = "setuptools", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "sympy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "triton", marker = "python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938 }, + { url = "https://files.pythonhosted.org/packages/c2/59/1e3160e18e12aa3038390efab3ce02b36a9d4d6a527ecdd8520dca2e68d8/torch-2.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:092790c696a760c729fd5722835f50b9d81fd7c8f141571f3f3cf4081a8f664c", size = 427199369 }, + { url = "https://files.pythonhosted.org/packages/01/79/1f2d34ad7034ee1c7ffc1cf8bf0f8213af2a81df6ecdb3997ecec107c09d/torch-2.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60fcdcb2f3876e21146cb4524ef06397d727ca9ad5f020818547e25075fe3cb7", size = 526574961 }, + { url = "https://files.pythonhosted.org/packages/6c/fd/0f2ce40f58aefbdb3392f9acce3c8171940943ae2d661f70558bfa73befb/torch-2.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a0d8b11f16a48d60e2015d8213aa0390744cbebb98e58b62b3514dddc656e330", size = 122015870 }, + { url = "https://files.pythonhosted.org/packages/c4/3a/ed0f4d4d1dcde03bced7aac9a28e800abcdc0cbd06b6775044c9fbd877b7/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", size = 111213045 }, + { url = "https://files.pythonhosted.org/packages/df/a9/f6a2a4d763ff1df02e9a64c477029db614295bc9367f4131223791ccc243/torch-2.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:572df8be8ffb4599c88cbd6a0726f1f854f4da65d2e3c09f0e2c2283333cd6d4", size = 427210998 }, + { url = "https://files.pythonhosted.org/packages/f3/82/fea946351658e6534db52d2cc12bc53087cbf87f9440c5f180f367c1950b/torch-2.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:796633c4cdf0fe2cdced72d8f88f22e73dbcfce83132763162f6d4bff13b820b", size = 526605292 }, + { url = "https://files.pythonhosted.org/packages/21/d6/e8f3c6f7e01f626f77259de9860d2a78bc84c40539e28e79b7e98b0bb659/torch-2.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:024c6cc0c1b085f2f91f20a3dc27b0471d021c31ce84b81be3afdc39f791fd9d", size = 122057313 }, + { url = "https://files.pythonhosted.org/packages/0d/fa/c1c10b7aff4a9a3e8956d4f0a5f468fa6db7abc3208805719076772b4833/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", size = 111213743 }, + { url = "https://files.pythonhosted.org/packages/11/18/9ecb37b56293a0be8d80f810bf672a72fe7e02f8b475d5ef1b9bf8a0d748/torch-2.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1e09d6a722504957c694faceca843acde562786df1144ebcc5a74075ec7f6005", size = 427213008 }, + { url = "https://files.pythonhosted.org/packages/d4/5a/7c50ba1b7b713d71d34669c6d13dab0a11531a3eceb0307a5162dbfec0f7/torch-2.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a3a9a21312872af8a26950b2c15680335a386a1f56ed03e780653d78b9607e9e", size = 526602329 }, + { url = "https://files.pythonhosted.org/packages/91/3d/e7adcc6aaf36961cd18f56cf8ad0f3058c3a5c84ccf391762176c94581b8/torch-2.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:49b58f1e2c52440abb6f17c28f0335fe6c6d01ad1a7f55b0183b81e4b34d64e6", size = 122057920 }, + { url = "https://files.pythonhosted.org/packages/36/76/6dcc7f0c07052102dd36f83cbc5800842a909c8c3fbf1a7f8a5844954de9/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", size = 111227066 }, + { url = "https://files.pythonhosted.org/packages/e9/09/2c10e8cd0e00fa5d23c052df6ce467eaa7182399f5e0f824f1e4ff42ccae/torch-2.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a3893dc2da0a972a8ca5d698c85a9f967559ac5f8ee1797b77408aa8734d073c", size = 427226309 }, + { url = "https://files.pythonhosted.org/packages/76/c6/22c2102bbef14ca6a6cb4c20e42f088e49c5f812be4e160ae57502e325f9/torch-2.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:49f1ea385c754e54919408a9bb3b5a72b0b755bbe2c916c1d6f70afbec4908a2", size = 526614507 }, + { url = "https://files.pythonhosted.org/packages/2b/0c/7d1deb6bce5bc3e6042caf39100ac768eba3b9a098e1dddd16f75bd6489b/torch-2.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f8573e3ce9ebcd53fe922f01077a6085ccdfbe5f12fd215883a9d87d7a744fd", size = 122051871 }, + { url = "https://files.pythonhosted.org/packages/f4/ce/aa8b7f9949d32e0f2f624f342bc3b48112c1b8a130288465938bc83bcbf9/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", size = 111537025 }, + { url = "https://files.pythonhosted.org/packages/69/d1/491e3a0389430946145888b0203f2b6a759ce2a61481b96a85c2da4f2ced/torch-2.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:31061ff56ed8fbf26c749806905aeb749ebeb819810fd5d52508aa5afd90dddc", size = 427219769 }, + { url = "https://files.pythonhosted.org/packages/9a/1d/38006e045bf0a1fc28ef01e757c554e59e59a8770c284bc4f47b14e60441/torch-2.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:cc26eead4cf51d0b544e31e364dcf000846549c273bd148936fe9d24d29acb92", size = 526571320 }, + { url = "https://files.pythonhosted.org/packages/56/94/655c91992a882bd5071aa0b5d22a07dbb130d801e872be97c0b627a7c693/torch-2.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a7de8a313090dc5c7d7ba4bfe5c3be222528f9a4dba1acc83bddb1157360c4b8", size = 122306773 }, +] + +[[package]] +name = "torchvision" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pillow", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "torch", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/b2/1e010052079e4c577007b789db336ea7075f1a426e84d17121fbc3745516/torchvision-0.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83fe6c020866a85acd7d97deccc45ff11d66daf42916d04396a4309c66c0ccb8", size = 1856017 }, + { url = "https://files.pythonhosted.org/packages/27/be/1b9c5de9c655ca2df4a74100fa671a7b848532ff787e077ccde14a7dea2a/torchvision-0.28.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5a38bc6da3d72621be003400b66f66a2b4c6d644fde05f680c2cb7ca8cf8dd6c", size = 7841822 }, + { url = "https://files.pythonhosted.org/packages/0b/9b/f1e68e861d4462e3e195a642c2b448e7b7d3fad5f209487162b9a2133d9b/torchvision-0.28.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7e80f543b22503d9415e126db5f0ff3917036925e38560ee6b9ae38c571a4002", size = 7670718 }, + { url = "https://files.pythonhosted.org/packages/f5/de/1494610ff54cbb154beb55033cc2cd50f3de04dac132fa2dd00e4f2b2556/torchvision-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:9a45ea67235d965ef52187130d20002a4de20c54ea3d927a24286961d268dc37", size = 3814319 }, + { url = "https://files.pythonhosted.org/packages/15/49/c1cab1ecbb3ff1a380a3f99283db1dee61b8afe354f6352c643b65937130/torchvision-0.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e9f54c30cd52e3ef7fd034cc69b7bb7e0964e1c8f8743e018ab92e95b40f9eee", size = 1856020 }, + { url = "https://files.pythonhosted.org/packages/f0/4c/95233776e2def960e5abb7a07931230a545f43717a56a1e1140162033598/torchvision-0.28.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5cf78ebc401ce64ae19b8c55de866bb836797d559a4de9c25ccbe74cfa642d3a", size = 7842127 }, + { url = "https://files.pythonhosted.org/packages/93/e4/e9b2495d0d57b9f60d63c57d0a910410a81b4b073bf70917bef815291119/torchvision-0.28.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:028a3d481b37d785605620d7cdad897064c5a55bae2aa1f2658766333e291940", size = 7675040 }, + { url = "https://files.pythonhosted.org/packages/7c/9c/55ed9cb6dfe3ee9c837df5cd0e758372e5829aa38b8dd71343aa632cc4e2/torchvision-0.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:87dc16b2df427c1318ad335f1e2be2b3b15b2cf20f7934c83b0505a48425ee5d", size = 4085785 }, + { url = "https://files.pythonhosted.org/packages/20/55/08a726c14c67b37c8aca04b077766909f1c7ed23f76116884fe63b9bd033/torchvision-0.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d483b4aa3f5237569053f749cd1a2b5bb548ca456e40461a5dd087f21149d123", size = 1856021 }, + { url = "https://files.pythonhosted.org/packages/db/8f/40beacd53809194f5259e590d1afaeaa8ad57da15f77c646e6560bcc4616/torchvision-0.28.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:bb6dd6918460ed89cc7644adcc2402991474d6933cf1ce92b390641cb233fddf", size = 7797014 }, + { url = "https://files.pythonhosted.org/packages/32/db/062cdb5a84380a60439775311fff34d89229760d2a50680393dc18699956/torchvision-0.28.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad7b3a439265cc3739a4ab5b4c998c0e38ea99c0ee7ca4dea35c5d0b099ec237", size = 7674669 }, + { url = "https://files.pythonhosted.org/packages/f3/a6/b4081e2d04e1541abf82785ac9e5178a494c19330391f551356c8c18b7b3/torchvision-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:7e9dd6f60d6e15f8dc27d4f877fdb6002fc70d70272412135f1c2ff9cfa08d3b", size = 4157380 }, + { url = "https://files.pythonhosted.org/packages/c5/b9/da40eca5bbe9596c12ae9899ab7abaf887f5e20f29d08b924b4633714821/torchvision-0.28.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3bd9dba55224a9db4a2d77f6feaa5651770d8c8e86d3d0ddb0fa6bec54c8712b", size = 1856014 }, + { url = "https://files.pythonhosted.org/packages/06/d6/313aafd3df4eaf5f330211bd4e75b7598bddbfee4f55580d3b58536e1b20/torchvision-0.28.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:89f90e29b0966352811b12589f3a3c61943bf2bb9487b9d7bbec10efb1096bb5", size = 7796873 }, + { url = "https://files.pythonhosted.org/packages/b3/41/31f8e959ab8f942600b6357f8999c21d779d5fd3304b0fd204ff4b518239/torchvision-0.28.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:36beb0782976906069ca03d4c9aacaf4b6b838b06ed6c20960ea9c51cce7acdd", size = 7674634 }, + { url = "https://files.pythonhosted.org/packages/15/15/4c5115253fd470672cdac0a1cf139e06b4f3e29d041238a2b255937f63be/torchvision-0.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:3557cc7b539f46dabcda2b6f2b14017ccbeef024de466d4fc5835fc3f287f769", size = 4184005 }, + { url = "https://files.pythonhosted.org/packages/6a/80/822a6163da716f8a78141cf6678d74e26a572285d4ea866ef8aa657bb307/torchvision-0.28.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:09ce8f56e81f19b9c378ae7bb109f83f6659fd8bc3cd14241a48e4af46e9ed49", size = 1856011 }, + { url = "https://files.pythonhosted.org/packages/7f/d1/cd3f9463b39a790ec8c0c2f6e6c8061edb1562114d04fcdfa786ed889345/torchvision-0.28.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:62c7d110f86a039245b587e4fae60278c649f3bd42ff79cfbc1178eca4e72542", size = 7796742 }, + { url = "https://files.pythonhosted.org/packages/d9/82/3e0a7ad18e99831e2d7f4713d3be717b7159ff5a920862dd5c23c454aa71/torchvision-0.28.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:904cf89af220f8c6b2ed0296bb5065b474ce43b77558e48b2bf9de8b0ba17204", size = 7675526 }, + { url = "https://files.pythonhosted.org/packages/18/d4/23aea03b28297bc66a4461f55ae4296368a9d85fa9a454bafcb2a5348bd7/torchvision-0.28.0-cp314-cp314t-win_amd64.whl", hash = "sha256:46f581979c010ad6da6bd85ee602aa707e1ff44312670223b7a0ee517ad06d47", size = 4291452 }, +] + +[[package]] +name = "tqdm" +version = "4.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184 }, +] + +[[package]] +name = "transformers" +version = "5.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "packaging", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "pyyaml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "regex", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "safetensors", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tokenizers", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "tqdm", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typer", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/e6/4134ea2fbea322cddc7ffc94a0d8ee47fe32ce8e876b320cd37d88edfc4d/transformers-5.8.1.tar.gz", hash = "sha256:4dd5b6de4105725104d84fd6abd74b305f4debfc251b38c648ee5dd087cf543b", size = 8532019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/b1/8be7e7ef0b5200491312201918b6125ef9c9df9dd0f0240ccef9ac824e6b/transformers-5.8.1-py3-none-any.whl", hash = "sha256:5340fb95962162cdfdae5cc91d7f8fedd92ed75216c1154c5e1f590fcf56dd0e", size = 10632882 }, +] + +[[package]] +name = "tree-sitter" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/03/5600b84aff2e6c4fe80cfebb4063fe2f50299521befe5f6092ab8c082f4a/tree_sitter-0.26.0.tar.gz", hash = "sha256:b40c219edccc4564530c96f8f1556f6202b37cda964d1cbd7bd2b7e68b40a245", size = 191423 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/18/78aae7e4b5a36daaebb0276e4b07d084d45298758000787838e89329e11f/tree_sitter-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1d6fe0e8fb4df77b5ee816228e2c4475a63d8cc1d4d3a7ffd7097b2b87fc3e95", size = 148679 }, + { url = "https://files.pythonhosted.org/packages/24/e4/b371b9553b0e47d130fc2073e56cab94fecc868be04666bf5bbd1fcd1cc9/tree_sitter-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:514a9bf8993e5210e7970736aaf6020d1759b670e195ef17b1c48f586aa30736", size = 140759 }, + { url = "https://files.pythonhosted.org/packages/22/7d/266fb0f2c41e6fb00b0f40e7a3338cdf99651e6a6511ca72bc78fc697636/tree_sitter-0.26.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10f0d4eb94aa7242dcb7f554bcd24dd7ba1c114f00d58759ba08c7a46c8ec51a", size = 637206 }, + { url = "https://files.pythonhosted.org/packages/40/9f/47cf22febb47132d5b3a507a27bb99ef89fe5c8ec420a13c6daa9b64f782/tree_sitter-0.26.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:335294ce0504fcefde5245dff596778ffaf820205b98ae0b549c72e48855f1d8", size = 664758 }, + { url = "https://files.pythonhosted.org/packages/4c/4d/8d144ca3beb46a62a5102b6deac76bb0da55235c2c7840faf3b12f2e9d97/tree_sitter-0.26.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9997ba61368c48ed54e715676afadf703947a1542464e39d047764fb3624b01", size = 647438 }, + { url = "https://files.pythonhosted.org/packages/4d/ed/ed1d6e78520c4fb64ed52fec3f2947bf8c1fbad7bc24e282c56193c9ba42/tree_sitter-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c56581ad256c4195a21bfe449fed5d44a02fe83a4a7d6e70e6ec302c881191c7", size = 661944 }, + { url = "https://files.pythonhosted.org/packages/10/83/45f5bd43db1b8248d2fd08ef6cbe43e2725c539e09a2cfb8bc2818646788/tree_sitter-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f8793fd18ad7eec276ed4b51c097b4bf2002b357259b66b0d75db1f3f41c754", size = 129496 }, + { url = "https://files.pythonhosted.org/packages/f1/8d/be68e6c04563eb54145424cc83fe0aa8b0ba6c90d8989cf8a032671b5f16/tree_sitter-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:dea4b4e27d49e9ec5b785d4f994da000e6726882fcc6ad05ec98478500c71aef", size = 116484 }, + { url = "https://files.pythonhosted.org/packages/87/ca/565702c44815393e3a973552ad546db4e5ca081ca8698640b4e93d809f51/tree_sitter-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6cb2bd20efb2544c19ac54486ab7cb8ec7b36f913bbe1ce95df84acb96743d9c", size = 148934 }, + { url = "https://files.pythonhosted.org/packages/54/6f/8bb61957f16ec1b1d92410a006cdc84a952b6352a7313b2ad299f2d21484/tree_sitter-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:918d89529786873f0982a0f59c2a303cd065fbfd1b903d71a8e4e1584f67b42e", size = 140820 }, + { url = "https://files.pythonhosted.org/packages/78/0a/8a6f08559182643a814a4ab559948ae817b2851890fd9b995a4fff6541ce/tree_sitter-0.26.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30a88be89ff1f2755297f81e8080d88b795dd98720c3f9fa2acf93873182cc95", size = 638844 }, + { url = "https://files.pythonhosted.org/packages/8a/2f/6e6781b31677231366cb3cf27bc8269157f6d4b03c9032865a4f5f2bbe7e/tree_sitter-0.26.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a6b333b0282d8bb0af741f9b018bd2523d4eecb2686bf6717066a625fecfaa4", size = 667487 }, + { url = "https://files.pythonhosted.org/packages/02/0b/0483078c8567445557a7015b0e5b187f6d7d4fda73464df9c4bdea7f7f3c/tree_sitter-0.26.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f3c44339dd34fe8eb2b8d5aa7610660499a795f70376b130bbee7a437337280", size = 647975 }, + { url = "https://files.pythonhosted.org/packages/27/68/da83ca72c984e96ab4eb3bee0db1a6ffb5de1c8c455f92bd9f420cde7f0e/tree_sitter-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94550e13b6ae576969da40246f4c4abb206380b5375ad43f26dd9151d55438e3", size = 665018 }, + { url = "https://files.pythonhosted.org/packages/d1/36/4d67927fd47b89af4a00f65f55a7370e28778cd50e972c2430487e3ecc27/tree_sitter-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca89e361a276dbc934b28a43dd881199e25d34ff5493ee0ce45f3c52a6124a37", size = 129619 }, + { url = "https://files.pythonhosted.org/packages/ed/72/cdefad523eb78710679c6da6a79e3d90f5afd32b1c6aa5a17bac7eef99f6/tree_sitter-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:bc6cb01d5ee75c85424aa1f1c72a82d8f07fd52539a0f3c4a6ed3e8721079b84", size = 116545 }, + { url = "https://files.pythonhosted.org/packages/cb/b0/465257cf8f972ad9f9812ec1cbaa8ec210ebebb601ade9a15881aa2436b4/tree_sitter-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed0889dbed843ce45ede9f5169c0b2dea2222f12685844a03fadb81f12705867", size = 148893 }, + { url = "https://files.pythonhosted.org/packages/a1/ec/19d093e854b45e807fecfdd26105c266f43aeecc39c4dc97992a7074ad5a/tree_sitter-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6189c6c340c7384357711e3d92645e96bfb79f7a502f86de1ebdb23eb43f7dab", size = 140829 }, + { url = "https://files.pythonhosted.org/packages/9b/ee/87e74671ed63a837e7a1f17ab94aa3913871e033b27523d8e7b83d6f7ad0/tree_sitter-0.26.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ff2e0750b7daa722302838356d7b65e303829b7eb73c915df127ddba115e1d1", size = 639334 }, + { url = "https://files.pythonhosted.org/packages/66/e7/f7e04cd9dff6b6ac0adf23922796fbc76accd4cf4bcda50542748d485679/tree_sitter-0.26.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7075ef857ef86f327dbb72d1e2574dda78db5754b3a1fca6506acd7fe5d561a7", size = 668102 }, + { url = "https://files.pythonhosted.org/packages/d3/90/0bfb16b7894fea728c774a89d5af421a9368a2f913bbd4e8dcab7caaecfb/tree_sitter-0.26.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:26c996c1edfee86e977bb3f5462e74fcec0d0b0db1e85a3c475875763caa03be", size = 648560 }, + { url = "https://files.pythonhosted.org/packages/cd/e6/0fe05ba396e9623b0ae40ccf34171336b8701ec8d7bd0ee9f5224d638665/tree_sitter-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00289bfe7978f3e0dc0ce69813a20fa9f44ea4c100b3ec62043e5eb74ccfc3a2", size = 665121 }, + { url = "https://files.pythonhosted.org/packages/eb/d2/a944b1ca35bed6068dc84a9967aaf3049d8cc0b7a36179eea8787270a6ab/tree_sitter-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:93e220cab7e6a823efeb2046c49171427de92ef71c7c681c01820d14d8d3721f", size = 129615 }, + { url = "https://files.pythonhosted.org/packages/09/ef/c7ca48293580d2249f36940c4eed5b4ddeb9ce75baf9a4ef30621987e0c7/tree_sitter-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:b31a8195d2f224224c530ac814632d98c1dcc123d227442c07c736e86b70d564", size = 116525 }, + { url = "https://files.pythonhosted.org/packages/c5/7a/4d84e6f6ae2c3e757490dd84de251712c31e293dfe31f28da1ec019cefa2/tree_sitter-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5a3c93a352b7e6f70f73e121bbfa2d0117ba7478bd51114ed35c91b0b78814fa", size = 148901 }, + { url = "https://files.pythonhosted.org/packages/b0/d9/efe62ec65dc9d096e834d27b8c058127e2146e42ff3380b822a233f016a6/tree_sitter-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5fc2f41bf246ff2f70a9cc3690be35ec7580a4923151873d898c8bcb1a4503d3", size = 140805 }, + { url = "https://files.pythonhosted.org/packages/c4/2c/c82326b7b97e3c485c18679883b16f89e5e913c639d3b219d3da70c9e67e/tree_sitter-0.26.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8ea92a255c91671a7ec4625aba3ab7bb5220c423630ffbf83c45d7312abe084", size = 640586 }, + { url = "https://files.pythonhosted.org/packages/e2/7a/f56e7d8282859452611024c7cbc623bfba5b24b8cb9b8f8bc88c5219fe9a/tree_sitter-0.26.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f665510f0fcf4636fb9696f1f7853bed7a3bd764b7bb0cb8494e619c14ed5a0c", size = 668300 }, + { url = "https://files.pythonhosted.org/packages/91/51/240ee81b9d5e9ca0a6cb1528e8605ffa70ab58c89ce126631be96d3e4bae/tree_sitter-0.26.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:253df7ab82cc0a9d311cd65f06e9f99fb3eac55996ae9fc94da22f123a861b90", size = 649627 }, + { url = "https://files.pythonhosted.org/packages/6a/54/760035cefedf9eb44f0f84c4ac22f1322e73155853e272576ee876336312/tree_sitter-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ff80d4833d330a73184a3ac5132abe93c575d2dea31975c6f15c0d21fef238aa", size = 664885 }, + { url = "https://files.pythonhosted.org/packages/c9/1b/0b36fe2a984ecedc4ce6aefd5d56447a6626a8e9b595c4e48658510ce8f8/tree_sitter-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:a4033fecc8f606c7f2e8b8014d0057b74668a7f0152763606f7bc25c5f9ec64c", size = 132688 }, + { url = "https://files.pythonhosted.org/packages/4d/74/ebc041a13fbf40144afdb0d4b447e48e0b4012ca866c63de8b48f801f0c1/tree_sitter-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:823251c4b6725a7c03ed497a339135ede7ae4bdde75bb8be7ef5e305aeb4ff52", size = 120287 }, +] + +[[package]] +name = "tree-sitter-c" +version = "0.24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/c9/3834f3d9278251aea7312274971bc4c45b17aec2490fd4b884d93bd7019a/tree_sitter_c-0.24.2.tar.gz", hash = "sha256:1628584df0299b5a340aa63f8e67b6c97c91517f52fa7e7a4c557e40adb330a9", size = 228397 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/c1/26ed17730ec2c17bedc1b673349e5e0a466c578e3eb0327c3b73cf52bf97/tree_sitter_c-0.24.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4d4579a8b54f0a442f903d88d3304cab77cd5c2031d4015baa4f2f8e15d6dcb7", size = 81016 }, + { url = "https://files.pythonhosted.org/packages/c1/1c/1140db75e7e375cda3c68792a33826c4fd40b5b98c3259d93c75f6c8368f/tree_sitter_c-0.24.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:97bc80a224d48215d4e6e6376bf30d114f4c317b8145ff1b02afe785d4ba7bdd", size = 86213 }, + { url = "https://files.pythonhosted.org/packages/e9/8c/0dfb88d726f8821d1c4c36042f092be974a800afd734307a595b8604190c/tree_sitter_c-0.24.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5041ef67eb68ce6bc8bb0b1f8ef3a5585ce523dae0c7eec109ab0627dd75aede", size = 94264 }, + { url = "https://files.pythonhosted.org/packages/87/78/47dc570e7aee6b0a1ecc2520b30639cc2b06003154c9ab0672d86bf720d5/tree_sitter_c-0.24.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c098bedcd5ac86ff93fa734d51d1dd86aed40fd5ed7d634c7af11380a0469969", size = 94560 }, + { url = "https://files.pythonhosted.org/packages/29/37/75d59d3f74f4cfc00f04472917e933d8a9c9fdc6eff980ef9552e010e6aa/tree_sitter_c-0.24.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:82842c5a5f2acd93f4de10038c33ac179c8979defc39376f990348d6289e933b", size = 94023 }, + { url = "https://files.pythonhosted.org/packages/64/57/8fc655d5a446a70a637e92b98bd2fdaab88bf5bb5b36076ac4add544808d/tree_sitter_c-0.24.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2b42e8e22202c251f8629306f9321233542e07a6e01611b5fe83489272143eb", size = 94160 }, + { url = "https://files.pythonhosted.org/packages/c1/f7/72a1d6b42dd31fd37e03ff67e7dc5ee572301499e6b216002b8dd42a1714/tree_sitter_c-0.24.2-cp310-abi3-win_amd64.whl", hash = "sha256:abb549225091f7b25df2dd3a0143ece6e208f7055d8bcb4700b41ee79b9ef1e1", size = 84669 }, + { url = "https://files.pythonhosted.org/packages/e2/9d/7475d9ae8ef679aa36c7dfe6c903ab78e573651c68b6ef9862d6a3f994db/tree_sitter_c-0.24.2-cp310-abi3-win_arm64.whl", hash = "sha256:4a2f4371cd816cc3153458f69062135ebb2ea5f275ddd90494e5c823d778204a", size = 82956 }, +] + +[[package]] +name = "tree-sitter-javascript" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/e0/e63103c72a9d3dfd89a31e02e660263ad84b7438e5f44ee82e443e65bbde/tree_sitter_javascript-0.25.0.tar.gz", hash = "sha256:329b5414874f0588a98f1c291f1b28138286617aa907746ffe55adfdcf963f38", size = 132338 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/df/5106ac250cd03661ebc3cc75da6b3d9f6800a3606393a0122eca58038104/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc", size = 64052 }, + { url = "https://files.pythonhosted.org/packages/b1/8f/6b4b2bc90d8ab3955856ce852cc9d1e82c81d7ab9646385f0e75ffd5b5d3/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1", size = 66440 }, + { url = "https://files.pythonhosted.org/packages/5f/c4/7da74ecdcd8a398f88bd003a87c65403b5fe0e958cdd43fbd5fd4a398fcf/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dc04ba91fc8583344e57c1f1ed5b2c97ecaaf47480011b92fbeab8dda96db75", size = 99728 }, + { url = "https://files.pythonhosted.org/packages/96/c8/97da3af4796495e46421e9344738addb3602fa6426ea695be3fcbadbee37/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:199d09985190852e0912da2b8d26c932159be314bc04952cf917ed0e4c633e6b", size = 106072 }, + { url = "https://files.pythonhosted.org/packages/13/be/c964e8130be08cc9bd6627d845f0e4460945b158429d39510953bbcb8fcc/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dfcf789064c58dc13c0a4edb550acacfc6f0f280577f1e7a00de3e89fc7f8ddc", size = 104388 }, + { url = "https://files.pythonhosted.org/packages/ee/89/9b773dee0f8961d1bb8d7baf0a204ab587618df19897c1ef260916f318ec/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b852d3aee8a36186dbcc32c798b11b4869f9b5041743b63b65c2ef793db7a54", size = 98377 }, + { url = "https://files.pythonhosted.org/packages/3b/dc/d90cb1790f8cec9b4878d278ad9faf7c8f893189ce0f855304fd704fc274/tree_sitter_javascript-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:e5ed840f5bd4a3f0272e441d19429b26eedc257abe5574c8546da6b556865e3c", size = 62975 }, + { url = "https://files.pythonhosted.org/packages/2e/1f/f9eba1038b7d4394410f3c0a6ec2122b590cd7acb03f196e52fa57ebbe72/tree_sitter_javascript-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:622a69d677aa7f6ee2931d8c77c981a33f0ebb6d275aa9d43d3397c879a9bb0b", size = 61668 }, +] + +[[package]] +name = "tree-sitter-python" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/c992ff0e768cb6768d5c96234579bf8842b3a633db641455d86dd30d5dac/tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac", size = 159845 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/64/a4e503c78a4eb3ac46d8e72a29c1b1237fa85238d8e972b063e0751f5a94/tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361", size = 73790 }, + { url = "https://files.pythonhosted.org/packages/e6/1d/60d8c2a0cc63d6ec4ba4e99ce61b802d2e39ef9db799bdf2a8f932a6cd4b/tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762", size = 76691 }, + { url = "https://files.pythonhosted.org/packages/aa/cb/d9b0b67d037922d60cbe0359e0c86457c2da721bc714381a63e2c8e35eba/tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5", size = 108133 }, + { url = "https://files.pythonhosted.org/packages/40/bd/bf4787f57e6b2860f3f1c8c62f045b39fb32d6bac4b53d7a9e66de968440/tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b", size = 110603 }, + { url = "https://files.pythonhosted.org/packages/5d/25/feff09f5c2f32484fbce15db8b49455c7572346ce61a699a41972dea7318/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d", size = 108998 }, + { url = "https://files.pythonhosted.org/packages/75/69/4946da3d6c0df316ccb938316ce007fb565d08f89d02d854f2d308f0309f/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683", size = 107268 }, + { url = "https://files.pythonhosted.org/packages/ed/a2/996fc2dfa1076dc460d3e2f3c75974ea4b8f02f6bc925383aaae519920e8/tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76", size = 76073 }, + { url = "https://files.pythonhosted.org/packages/07/19/4b5569d9b1ebebb5907d11554a96ef3fa09364a30fcfabeff587495b512f/tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb", size = 74169 }, +] + +[[package]] +name = "tree-sitter-typescript" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/fc/bb52958f7e399250aee093751e9373a6311cadbe76b6e0d109b853757f35/tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d", size = 773053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/95/4c00680866280e008e81dd621fd4d3f54aa3dad1b76b857a19da1b2cc426/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478", size = 286677 }, + { url = "https://files.pythonhosted.org/packages/8f/2f/1f36fda564518d84593f2740d5905ac127d590baf5c5753cef2a88a89c15/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8", size = 302008 }, + { url = "https://files.pythonhosted.org/packages/96/2d/975c2dad292aa9994f982eb0b69cc6fda0223e4b6c4ea714550477d8ec3a/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31", size = 351987 }, + { url = "https://files.pythonhosted.org/packages/49/d1/a71c36da6e2b8a4ed5e2970819b86ef13ba77ac40d9e333cb17df6a2c5db/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c", size = 344960 }, + { url = "https://files.pythonhosted.org/packages/7f/cb/f57b149d7beed1a85b8266d0c60ebe4c46e79c9ba56bc17b898e17daf88e/tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0", size = 340245 }, + { url = "https://files.pythonhosted.org/packages/8b/ab/dd84f0e2337296a5f09749f7b5483215d75c8fa9e33738522e5ed81f7254/tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9", size = 278015 }, + { url = "https://files.pythonhosted.org/packages/9f/e4/81f9a935789233cf412a0ed5fe04c883841d2c8fb0b7e075958a35c65032/tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7", size = 274052 }, +] + +[[package]] +name = "triton" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887 }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306 }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359 }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725 }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629 }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241 }, + { url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764 }, + { url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537 }, + { url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760 }, + { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404 }, +] + +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660 }, +] + +[[package]] +name = "typer" +version = "0.26.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "colorama", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" }, + { name = "rich", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "shellingham", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/f7/68adc395201b20b872d68e975386832e8005ffeacedd43a1d837a32815be/typer-0.26.8.tar.gz", hash = "sha256:c244a6bd558886fe3f8780efb6bdd28bb9aff005a94eedebaa5cb32926fe2f7e", size = 202097 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/87/b9fd69c92c6102a066e1b86a35243f53e70bd4c709f2a26d9f4fee4f4dc0/typer-0.26.8-py3-none-any.whl", hash = "sha256:3512ca79ac5c11113414b36e80281b872884477722440691c89d1112e321a49c", size = 122564 }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750 }, +] + +[[package]] +name = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168 }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087 }, +] + +[[package]] +name = "uvicorn" +version = "0.52.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "h11", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/0f/3f86e61397dd33bf2ccf28188c40db6a740658aeebbbf6e7dbc101a1f487/uvicorn-0.52.4.tar.gz", hash = "sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86", size = 100627 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/79/4a20b54ab0491485ccd8c077db2d39187c7f12b3e15485d38a7be37c81b4/uvicorn-0.52.4-py3-none-any.whl", hash = "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1", size = 79871 }, +] + +[[package]] +name = "websockets" +version = "16.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/f7/bc3a25c5ec26ce62ce487690becc2f3710bbc7b33338f005ad390db0b986/websockets-16.1.1.tar.gz", hash = "sha256:db234eda965dcce15df96bb9709f587cd87d4d52aaf0e80e2f34ec04c7670c57", size = 182204 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/03/47debfe28e9d6d354be5d777b67fd44c359b9eb299a5d103500bd7cc3e37/websockets-16.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d0fcf657e9f13ff4b177960ab2200237b12994232dfb6df16f1cfe1d4339f93c", size = 179566 }, + { url = "https://files.pythonhosted.org/packages/72/93/31efa1ed78c17e5cfc229fd449e3966e1b9cc15753204cd585cc8dd01f4a/websockets-16.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b852788aa51764e2d8e4cf5493d559326bcae5e38d16ba25ffa322b034df272a", size = 177250 }, + { url = "https://files.pythonhosted.org/packages/01/4a/542378ab3972b0c1cf1df3df3eff9591cea0d30c58c3aa3c4ddbc244e787/websockets-16.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1427fb4cf0d72f66333e2cacc3ff5f575bf2d7008166ce991a4a470b21d51a22", size = 177528 }, + { url = "https://files.pythonhosted.org/packages/33/d9/162321f63c7eed558e9e1798ed7a1e34a4f6dab51f35419e4ed7a4907979/websockets-16.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:da4ca1a9d72f9030b3146b8d7022719a9f3d478f61efe6f7dd51d243f61c51b2", size = 186859 }, + { url = "https://files.pythonhosted.org/packages/de/09/87df740f7430ce564bd52402e9c9458d4d0459cc7d2ee29e530c8204851b/websockets-16.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86d7f0f8bdb25d2c632b72527325e4776430fd5bc61b9118de4e2b8ddb5f5b01", size = 188095 }, + { url = "https://files.pythonhosted.org/packages/d2/12/3d2703af7cc095f3c81904c92208cc1ae79affbc67376944b50ee9301f73/websockets-16.1.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7dfcad78ea1492ee3a9ec765cb7f51bbc17d477107aaf6b22abf7b2558d1c5a0", size = 191385 }, + { url = "https://files.pythonhosted.org/packages/1d/69/986aa0234a964a00f5149cfc46e136e96c8faad1c783474550f40d31aef4/websockets-16.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb9a0a6dc3d1b3986cb88091b6899f0396651e0f74e2c9766ab8d6ffc3842e29", size = 188653 }, + { url = "https://files.pythonhosted.org/packages/35/6b/10f9d03e3970a69ba67bd3b46b87a929b586d0300fadbfe14f57c1f85490/websockets-16.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29dfa8114c4a620c69591c5973860f768eac29d3fd6904f37f34266cb219c512", size = 187426 }, + { url = "https://files.pythonhosted.org/packages/56/db/bb3aad62bf63d8bb3f0634b2eabffcfb3677a34bd19492110ff6869cf703/websockets-16.1.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ff9417c0ada4d0f7d212f928303e5579bdf3ace4c802fa4afabb30995da58c3", size = 184882 }, + { url = "https://files.pythonhosted.org/packages/6c/4c/c09a2ea9bfbeccce52fdc383e5f28af4bc8843338aabac28c81489af6120/websockets-16.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fe0b50da2d84535fb4f7b4bfa951280f97ce3d558a0443b541166d609e67b57", size = 187584 }, + { url = "https://files.pythonhosted.org/packages/c7/8b/31bb4eb4d9eaacf1fdd39d115772a8aeaedfc19b5dc262e57ffbc8a9d42c/websockets-16.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34420aaa64440ebd51ac72ca8a45ef4626429438c9b02e633ae412ed43f925d3", size = 186174 }, + { url = "https://files.pythonhosted.org/packages/2f/e4/dc02d725610a1ad49e193ef91a548194d71bdc6cdf27da83067dd1f73995/websockets-16.1.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a6a61aff018180c9c50b7b0da33bfd29d378af3497429c95006c589a23a11648", size = 187986 }, + { url = "https://files.pythonhosted.org/packages/e0/73/30ed84c8bfd14c73d4af29d5ed9323c3073b48e0b7b23b67070f4e7fd59b/websockets-16.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:04fd29a0e2fe9414a95b00e92c67ae51bf900c50c0f8a4b2dafdad621f49ea1d", size = 185565 }, + { url = "https://files.pythonhosted.org/packages/7d/d3/4be8d4959f51e31b4f8fc0ece12b45bd3b6c0d15ea23b9990d9c11fc805f/websockets-16.1.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5c31aa7e39ee3e8a358573257f1c0bb5c52430d1b637030dd9c8cc2c282926be", size = 186598 }, + { url = "https://files.pythonhosted.org/packages/26/fa/abb38597a52d84ed9cfacadc7a0c6f2db282c0ab23cdf72b58a666a21227/websockets-16.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d14bfb217eb4701e850f1525c9d29d79c44794cdf1c299ead25f39f8c78dea81", size = 186834 }, + { url = "https://files.pythonhosted.org/packages/59/80/1119ad08a228b90c4eb77fbe48df7836731a605f5f881ba701ca826a4a65/websockets-16.1.1-cp311-cp311-win32.whl", hash = "sha256:2e28e602bb13da44fbe518c1781a88e3b9d4c3d48d02c9bad83e546164336f57", size = 179940 }, + { url = "https://files.pythonhosted.org/packages/71/b2/e511c1c6f64a95c2f3fc54bffda0e14eaa7e9442be605c29270f7589b918/websockets-16.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:7421fad442de870a8cbf2287d1cad7e706ece0dbfeba5e911df132cbdc1cb56a", size = 180239 }, + { url = "https://files.pythonhosted.org/packages/17/9d/681cda21c9eee743203a6cb79b9d3d05adad9aa60ec660c6c9bf4dd619ca/websockets-16.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cc97814dfb786a83b6e2dc2e79351e1b83e6d715647d6887fcabd83026417a00", size = 179600 }, + { url = "https://files.pythonhosted.org/packages/fb/8d/6195a88b45e8d2a8f745fc2046e36f885a3c9763e6767d2c46229bf9510c/websockets-16.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e047dc87ef7ca50f4d309bf775ad4a71711c58556d75d7bd0604b2317f43e94b", size = 177272 }, + { url = "https://files.pythonhosted.org/packages/73/e3/fe2d498c64dea0095c9a9f9a351af4cd6eef31b618395582bc1f38ba45ff/websockets-16.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01fbdcbac298efe19360b94bc0039c8f746f0220ba570f327577bfee81059175", size = 177542 }, + { url = "https://files.pythonhosted.org/packages/fe/ed/f1831681fce0e3242346e5458486003c5f124ed69e5e0b847fd029db4973/websockets-16.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f62863e8a00a6d33c3d6566ec0b89f23787b747ffe0c3bc71ec0e76b82c94b1", size = 187137 }, + { url = "https://files.pythonhosted.org/packages/6f/79/4ff9dcc1bb46f6b4c536936dde1fd60f9b564f3304307274db97f4c9496d/websockets-16.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8087e82f842609734c9b5a1330464f8e94e346ba0e18c832c08bafa4b0d63c15", size = 188374 }, + { url = "https://files.pythonhosted.org/packages/62/c3/5c49b6efb36cab733d23773f6de575e1dba65736ead17d5d2b2a1daef779/websockets-16.1.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2bb5d041a8307d2e18782e7ce777f6fdb1e8c2f5d09291484b18c294b789d9aa", size = 191155 }, + { url = "https://files.pythonhosted.org/packages/6e/f6/56ccceda3a4838d18f1d40821480da4775397e8b1eecf4031e20c50e2e90/websockets-16.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1db4de4a0e95673f7545d393c49eeb0c2f18ac1ef93073218c79d5cdb2ee75ab", size = 189011 }, + { url = "https://files.pythonhosted.org/packages/86/d6/ad5286241a2bce1107e2798d3bfbd62cf79aee167bdb654f8cb1e9dbf949/websockets-16.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f17dbe07eb3ea7f99e4df9b7e0efefe80fbf30d37a8cc4d561a0aed310bc8847", size = 187766 }, + { url = "https://files.pythonhosted.org/packages/bc/67/d65c970b7e347fdca69479beb7811c2060529956730a7a4e3ae7c66b0e31/websockets-16.1.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b57693728576d84ede0a77987ab16881b783d2cd9f1dc180a8fbbc3f79c4428", size = 185173 }, + { url = "https://files.pythonhosted.org/packages/1d/5b/14af3cd4ee69d8ea9baca58f3dc3cfb1ba78332a347fd478cb096549d60e/websockets-16.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a636ff1e7a5c4edf71ef0e79adae7f25dba93b4fcbe3dc958733477ffeb0eaf", size = 187809 }, + { url = "https://files.pythonhosted.org/packages/7b/11/be301710d70de97e3e7b3586e6d492c9c06d6a61bf1c2202c36cf0c75607/websockets-16.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d6bec75c290fe484a8ba4cacdf838501e17c06ecfbbf31eede81a9e431bd7751", size = 186412 }, + { url = "https://files.pythonhosted.org/packages/db/07/fe1435bf6fe738a3d3b54dbe0c18dabf12cba4d909ac8b58b539ce27c1f4/websockets-16.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:54509b8e92fee4453e152b7558ddef37ce9705a044922f2095a6105e3f80c96f", size = 188290 }, + { url = "https://files.pythonhosted.org/packages/8a/0a/81f394aff8efcbb01208c1ced77df0a3c7fcce584a88c7273663697946c2/websockets-16.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f0aa4aad3b1b69ad3fd85a0fd0952ec64331c762bd77ec51cc814170873890b2", size = 185844 }, + { url = "https://files.pythonhosted.org/packages/39/5c/dd485b995473f415510251fe9bd708f2d24458f439fce958daf8d66dc7c6/websockets-16.1.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:42290eb6db4ccaca7012656738214f8514082fb6fa40cdeb61bb9a471b52e383", size = 186823 }, + { url = "https://files.pythonhosted.org/packages/9d/0b/f78de76ff446f1e66af12b43c48a35f31744de93cfdec2f4ea67d5d7bbf1/websockets-16.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:53260c8930da5771cec89439bff99c20c8cb03ddb9588b980697355a83cd4bd3", size = 187102 }, + { url = "https://files.pythonhosted.org/packages/37/a1/4cf892007778eaf84ad162bfc98046e0ed89b63ac55949e3236626b2a23f/websockets-16.1.1-cp312-cp312-win32.whl", hash = "sha256:1d27fa8462ad6a1cb36206a3d0640b2333340def181fae11ed7f9adeaa5c0747", size = 179943 }, + { url = "https://files.pythonhosted.org/packages/d9/de/6abe251d28c3a3f217096575400b27750b18e0b1d2fff3a2a239960fea07/websockets-16.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:b436f6ec4fc3a6b4237c84d3f83170ed2b40bb584222f0ac47a0c8a5921980c7", size = 180243 }, + { url = "https://files.pythonhosted.org/packages/ce/fd/6ec6c6d2850aea25b1b2aa9901a016980bb87d01e89b3eb00470b1b5d471/websockets-16.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ab59169ace05dcb49a1d4118f0bde139557adf45091bd85747e36bf5de984dd1", size = 179587 }, + { url = "https://files.pythonhosted.org/packages/5f/d8/1d299d2dd34087db39831a34cc645ef8a6f89d78efada6983093513cd81c/websockets-16.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5e3b7d601f6f84156b08cc4a5e541c2b50ad7b36cfc302b657a12477c904a5df", size = 177272 }, + { url = "https://files.pythonhosted.org/packages/3d/86/0a70d3ae2f0f2256bb41302d9804dbca65d4360281e7feb3e1f94102ac46/websockets-16.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cd2ca96a082a36964aca83e992f72abeb61b7306c1a6cba4c7d06a7b93750cac", size = 177530 }, + { url = "https://files.pythonhosted.org/packages/b5/c2/c676c69444d9db448b3f0a55a98dcc534affce0bce961d9d2f0b8499b10a/websockets-16.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f5d497865f05bb222cab7016c6034542e84e5f29f49c6fd3f4939cda7197b5b8", size = 187197 }, + { url = "https://files.pythonhosted.org/packages/0b/13/88137fbaf726ebe29d62c1117fa11fa2bbb6209dc79d4ad738efbe36a2aa/websockets-16.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bae954c382e013d5ea5b190d2830526bfa45ad121c326da0049b8c769f185db6", size = 188433 }, + { url = "https://files.pythonhosted.org/packages/01/6d/46c2f2ce6751cb26f39293e1ecbf8544cb01321397cd476c2756b98c216d/websockets-16.1.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e09f753a169951eb4f28c2c774f71069304f66e7277e0f5a2892423599cfa854", size = 189868 }, + { url = "https://files.pythonhosted.org/packages/29/2b/170a9e8097636cfde4dc3c592b6e00b18a44a2f5407606d96ca542dd5838/websockets-16.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:024193f8551a2b0eafbdd160911012c4e6c228c28430c84433253299a9e42d6a", size = 189059 }, + { url = "https://files.pythonhosted.org/packages/a7/48/f0d4ebc9ab4b473b8861b9e20fdb663d515d42f7befdf62cdb60fee7a1ec/websockets-16.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aabe464bfd13bd25f4821faf111da6fefdc389f870265a53105580e45b0a2e49", size = 187814 }, + { url = "https://files.pythonhosted.org/packages/d5/ba/39a41d3ae8e72696a9492581900611c5a91e2b07563b0bcd2523adea9854/websockets-16.1.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a28fcbc9b6baf54a2e23f8655f308e4ccc6afdd7266f8fe7954f320dcda0f785", size = 185229 }, + { url = "https://files.pythonhosted.org/packages/3c/36/ac15b604f850d1907f0a85ed721cefe47cd45034b3620069b829746cccbe/websockets-16.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79eace538c6a97e96d0d03d4f9d314f9677f5ed85a8a984992ffd90b13cb8a56", size = 187874 }, + { url = "https://files.pythonhosted.org/packages/a8/f3/3fbd5d71d59299c3770faa5884d4f45070236ca5a35ab3a61830812c409a/websockets-16.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:496af849a472b531f758dbd4d61338f5000538cb1a7b3d20d9d32a264517f509", size = 186469 }, + { url = "https://files.pythonhosted.org/packages/b4/fc/dd90349bba58af2a53ef2ddd9c32716c81eb6d59a0687939fff561860878/websockets-16.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5283810d2646741a0d8da2aa733d6aefa0545809afccb2a5d105a26bc45125f1", size = 188347 }, + { url = "https://files.pythonhosted.org/packages/4c/f3/f73ba86427682da59b78c11d77ba56d5b801c32e84afe79b274bbd6a9bb2/websockets-16.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4e3b680b1e0a27457e727a0d572fd81dffa87b6dbf8b228ab57da64f7d85aead", size = 185903 }, + { url = "https://files.pythonhosted.org/packages/34/7c/f95eb20e80104173b3a0a092291f89ea4047ef6e608e0a57ca06eb14eecb/websockets-16.1.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69159730a823dde3ea8d08783e8d47ef135a6d7e8d44eb127e32b321c9db8e3e", size = 186855 }, + { url = "https://files.pythonhosted.org/packages/b0/35/dd875b3e050ff232d60fa377707f890e369f74d134f1be32e8f68879747c/websockets-16.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ed5bb271084b46530ee2ddc0410537a9961152c5ccba2fc98c5276d992ccba87", size = 187140 }, + { url = "https://files.pythonhosted.org/packages/e8/dc/5cbfcb41824502f6af93b8f3943a4d06c67c23c7d2e31eb18748c4a5b2a7/websockets-16.1.1-cp313-cp313-win32.whl", hash = "sha256:cfb70b4eb56cac4da0a83588f3ad50d46beb0690391082f3d4e2d488c70b68ea", size = 179928 }, + { url = "https://files.pythonhosted.org/packages/b0/c1/71e5deb5b7f8f226997ab64908c184ac3105c0155ce2d486f318e5dd08a8/websockets-16.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9531d9cbeac99af6f038fb1bc351403531f7d634a2c2e10e2f7c854c6ed5b68", size = 180242 }, + { url = "https://files.pythonhosted.org/packages/73/a2/ba78a164eeea4620df4a4df4bd2ed6017438c4655cc0f36f2c0bc0432355/websockets-16.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:443aefe96b7fdb132e2a70806cca1f2af49bb3f28e47abcd7c2e9dcf4d8fa1b8", size = 179635 }, + { url = "https://files.pythonhosted.org/packages/b9/08/d26d7a7628cd4ac34cbbdb63ac80914ca842ed8e42938c40a53567806df3/websockets-16.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6456ff333092d509127d75a638cb411afae8ff17f092635015d1902efec8a293", size = 177320 }, + { url = "https://files.pythonhosted.org/packages/0f/45/ebec83e6269536aa5932533c67b0af5c781f3e73fdbcd68672dcf43f4f44/websockets-16.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fce6c48559c86d1ac3632ccb1bebc7d5442fbe79bd9bb0e40379ee54be2a4051", size = 177544 }, + { url = "https://files.pythonhosted.org/packages/c9/d5/abc614d2297f6c1c3e01e61260364457a47c25cc1cf6a879038902bc6aa8/websockets-16.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:92b820d345f7a3fc7b8163949ee92df910f290c3fc517b3d5301c78065adafe1", size = 187270 }, + { url = "https://files.pythonhosted.org/packages/52/71/4c99af3b87dff1b2927981f6876607d4acb45338c665242168d3982f7758/websockets-16.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a606d9c24035242a3e256e9d5b77ed9cd6bccfcb7cf993e5ca3c0f6f68fb6a7", size = 188509 }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5c8ca14b0df7eb84ed0524165c5359150210140817a3312aee57bf62a1cf/websockets-16.1.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:414e596c75f74e0994084694189d7dc9229fb278e33064d6784b73ffbba3ca31", size = 189882 }, + { url = "https://files.pythonhosted.org/packages/25/c1/bedfba9e70557129cb8083748d167bdcc01483dedf0f0df143676df05cbe/websockets-16.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:536676848fc5961aca9d20389951f59169508f765637a172403dc5434d722fa0", size = 189114 }, + { url = "https://files.pythonhosted.org/packages/df/09/aa835b2787835aebd839114be5de51b797cb480b63ba42b26d34dfe147cb/websockets-16.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97fd3a0e8b53efa41970ac1dff3d8cf0d2884cadeb4caaf95db7ad1526926ee3", size = 187861 }, + { url = "https://files.pythonhosted.org/packages/20/26/f6408330694dbc9830857d9d23bc14ac4f6875127a480cfdda8d5ca21198/websockets-16.1.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7b1b19636af86a3c7995d4d028dbe376f39b4bf31541146f9c123582a6c94562", size = 185286 }, + { url = "https://files.pythonhosted.org/packages/17/9a/e0675e70dd8a80762cf35bb18799d3f290a4890ffe6439bc51d222796083/websockets-16.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:41c8e77f17294c0ac18008a7309b99b34ee72247ef10b6dff4c3f8b5ac29896b", size = 187935 }, + { url = "https://files.pythonhosted.org/packages/33/c1/3234cfb86afde01b81e9bddcc6e534c440975d60a13991259e833069ab3e/websockets-16.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9f63bcef7f4b02b06b35fc01c93b96c43b5e88e1e8868676caacf493d5a31f3a", size = 186444 }, + { url = "https://files.pythonhosted.org/packages/89/87/9c15206e1d778923d8daa9657de07aa62ea815e13448319c98458c37b281/websockets-16.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dab9eb87869da2d6ed3af3f3adf28414baae6ec9d4df355ffc18889132f3436c", size = 188409 }, + { url = "https://files.pythonhosted.org/packages/f2/00/cf5de5c67676de2d3eef8b2a518f168f6796595447a5b7161ba0d012915c/websockets-16.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:43e3a9fdd7cbf7ba6040c31fae0faf84ca1474fef777c4e37912f1540f854499", size = 185958 }, + { url = "https://files.pythonhosted.org/packages/62/c0/731b6ddede2e4136912ec4cff2cffbda35af73546be4762c3d7bd3bd79af/websockets-16.1.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:056ae37939ed7e9974f364f5864e76e49182622d8f9751ac1903c0d09b013985", size = 186911 }, + { url = "https://files.pythonhosted.org/packages/8c/7f/39c634472c4469a24a7c09cecddffb08fac6d0e74f73881a94ee8a40a196/websockets-16.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0eadbbf2c30f01efa58e1f110eb6fa293261f6b0b1aa38f7f48707107690af9", size = 187204 }, + { url = "https://files.pythonhosted.org/packages/26/89/9667c256c256dafcc62d21328ce7a40067da857969b68ee9af375b0aaf72/websockets-16.1.1-cp314-cp314-win32.whl", hash = "sha256:195c978b065fa40910582464f99d6b15c8b314c68e0546549a55ed83f4735328", size = 179603 }, + { url = "https://files.pythonhosted.org/packages/bd/dd/1c099d6c0fc5deb6b46ccdbb6981fdb4b12c917869cb3952408409dc18db/websockets-16.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:4e8d01cc3bcae7bbf8167f944aeafefed590fae5693552bba9794a9df68371cc", size = 179948 }, + { url = "https://files.pythonhosted.org/packages/35/25/9956b2d5e0529d5d23924f21bba1440d4c5c88a562e4f08550871ffa97a7/websockets-16.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0ffd3031ea8bda8d61762e84220186105ba3b748b3c8da2ae4f7816fac03e573", size = 179963 }, + { url = "https://files.pythonhosted.org/packages/17/06/55ffc976c488b6aee9ea05761ff7c4e88e7c1fd82818c8ca7b556ad2f90c/websockets-16.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84a2cef8deffbd9ab8ee0ea546a2a6a7030c28f44e6cdd4547dbfeb489eb8999", size = 177497 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/f7dac2e980bacc92bdc26cebae4ae4d50cae5380732c50980598fc0bbae4/websockets-16.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3df13f73af9b3b38ab1195eb299ecb67a4330c911c97ae04043ff74085728abe", size = 177698 }, + { url = "https://files.pythonhosted.org/packages/b2/39/26762f734113e22da2b942c3aca85798e0c0405d64c256549540ff31e5a1/websockets-16.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:23253dd5bcae3f9aaee0a1d30967a8dbd52e5d3cff93a2e5b84df57b77d4750d", size = 187561 }, + { url = "https://files.pythonhosted.org/packages/11/94/c3f330851806b9b02138b774d593478323e73c99238681b4b93efe64e02d/websockets-16.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1c5705e314449e3308872fe084b8571ce078ee4fc55a98a769bdefe5917392", size = 188732 }, + { url = "https://files.pythonhosted.org/packages/d1/f2/eb2c450f052de334ae33cf200ece6e87b0e14d186807074e4eb1cd2cdea2/websockets-16.1.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69e52d175a0a7d1e13b4b67ad41c560b7d98e8c6f6126eb0bda496c784faf8c7", size = 190872 }, + { url = "https://files.pythonhosted.org/packages/70/31/2ac8cecf3a74f7fed9132129fc3d90b3998a1554570c11a69b2a8c20332d/websockets-16.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f79c89b5eb034d1722938a891916582f8f7f503f58ca22518a63c3f2cd18499", size = 189305 }, + { url = "https://files.pythonhosted.org/packages/6a/cf/8ab19650d3c0d4562c92e70ab47c257c4aa5c6a713ed87fe63766b31fefc/websockets-16.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39f2a024af5c345ffe8fcf1ee18c049c024c94df393bb09b044a6917c77bde43", size = 188033 }, + { url = "https://files.pythonhosted.org/packages/66/d7/a49a38a6127a4acb134fb1912b215d900cc657605cff32445bf519f3acc4/websockets-16.1.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:952303a7318d4cbe1011400839bb2051c9f84fa0a35923267f5daba34b15d458", size = 185748 }, + { url = "https://files.pythonhosted.org/packages/95/3e/ad1fa40388c7f2e0bb2c7930d0090b6c5498594bd1cdaec18864df3d9e97/websockets-16.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:249116b4a76063d930a46391ad56e135c286e4562a18309029fc2c73f4ed4c62", size = 188285 }, + { url = "https://files.pythonhosted.org/packages/35/b8/d5db28ca264b9104f82196f92dc8843e35fd391f763d42e4ad358f5bc97e/websockets-16.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:61922544a0587a13fd3f53e4c0e5e606510c7b0d9d22c8444e5fae22a06b38cb", size = 186777 }, + { url = "https://files.pythonhosted.org/packages/42/9c/726cb39d0cc43ae848dce4aa2acb04eecc6738b1264ec6d700bf6bcfb9f8/websockets-16.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:46dcaa042cd1de6c59e7d9269fa63ff7572b6df40510600b678f0826b3c7af51", size = 188682 }, + { url = "https://files.pythonhosted.org/packages/be/c7/1168704de8c2dd483edabe4a22cbe4465dd8be8dd95561d214f9fe092871/websockets-16.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:38565aca3e01ea8734e578fb2118dade0ecb0250533f29e22b8d1a7a196cf4d0", size = 186377 }, + { url = "https://files.pythonhosted.org/packages/ca/40/f9ff2d630ffce4e7dfea0b2288e1caf9ebbf9ff8a9ec9396136ce8b94935/websockets-16.1.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:42f599f4d48c7e1a3338fdaac3acd075be3b3cf02d4b274f3bf2767aedd3d217", size = 187148 }, + { url = "https://files.pythonhosted.org/packages/b5/71/e177c8299f78d7cbe2d14df228643c10c70c0e86e108e092056bbcc16e46/websockets-16.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcc04fedf83effaeb9cce98abc9469bb1b42ef85f03e01c8c1f4438ef7555737", size = 187578 }, + { url = "https://files.pythonhosted.org/packages/49/b2/b6987faf330f5af5c787a2610124c2e8403d51724f9001ec4fff6311fe7a/websockets-16.1.1-cp314-cp314t-win32.whl", hash = "sha256:8483c2096363120eea8b07c06ae7304d520f686665fffd4811fad423930a65d7", size = 179729 }, + { url = "https://files.pythonhosted.org/packages/a2/6e/fbac6ed878dd362fbad7d415fa4f84d38e3e33fed8cde45c64e783acf826/websockets-16.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bcce07e23e5769375158f5efdcdafa8d5cd014b93c6683865b840ed65b96f231", size = 180072 }, + { url = "https://files.pythonhosted.org/packages/e1/ed/71fea6e141590cafc40b14dc5943b0845606bee87bdb52a21b6a73eb4311/websockets-16.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:820fb8450edddae3812fd58cbc08e2bf22812cb248ecb5f06dbb82119a56e869", size = 177185 }, + { url = "https://files.pythonhosted.org/packages/01/ec/00e7eeca200facf9266a83e4cbbf1bed0e67fba1d4d45031d3e5b3d81b5c/websockets-16.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:125f22dbefaf1554fea66fc83851490edb284ce4f501d37ffed2752f418332d9", size = 177459 }, + { url = "https://files.pythonhosted.org/packages/75/fd/5774c4b33f7c0d8f0c51809c8b3a93456c48e3543579262cfa64eb5f522e/websockets-16.1.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30bbe120437b5648a77d3519b7024ea09530e0b5b18d3698c5a0ae536fe0cc2e", size = 178294 }, + { url = "https://files.pythonhosted.org/packages/37/c3/48e2c03d2bd79bb45948841c592d24156312dd5f58cdf8f549febe652fb6/websockets-16.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6b9dadbef0cccd9f4c4ee96b08898afa73e26803bbe0f6aeb5bb12b0074206d", size = 179190 }, + { url = "https://files.pythonhosted.org/packages/2d/3f/73e511ecf2496ceac57dd4ed8388efe2bcf0769338a2dbf242c8366ae87e/websockets-16.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56cd5fc4f10a9ea8aa0804bddb7b42506cf9e136046f3b4c27de8fec9e2ecba5", size = 180330 }, + { url = "https://files.pythonhosted.org/packages/be/4d/2d0d67834092e354d2b0498f014a41249a89556bc406cf86f3e1557bb463/websockets-16.1.1-py3-none-any.whl", hash = "sha256:6abbd3e82c731c8e531714466acd5d87b5e88ac3243465337ba71d68e23ae7e3", size = 173814 }, +] + +[[package]] +name = "xlsxwriter" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315 }, +] From 597382c43f98a76a10668326f302083c2136c73c Mon Sep 17 00:00:00 2001 From: nikhilgosavi-josh Date: Thu, 3 Sep 2026 12:28:41 +0530 Subject: [PATCH 8/8] restructured_ai-service --- .gitignore | 2 + apps/frontend/.env.example | 5 + apps/frontend/vite.config.js | 31 +- docker-compose.yml | 3 + services/ai-core-services/.agents/AGENTS.md | 16 +- services/ai-core-services/README.md | 8 +- .../src/api/{v1/.gitkeep => __init__.py} | 0 .../.gitkeep => api/v1/__init__.py} | 0 .../src/api/v1/meeting_bot.py | 57 ++-- .../ai-core-services/src/api/v1/parsing.py | 46 +-- .../ai-core-services/src/common/__init__.py | 0 .../ai-core-services/src/core/__init__.py | 0 .../src/interview_analysis/__init__.py | 0 .../src/meeting_bot/__init__.py | 14 +- .../src/meeting_bot/attendee.py | 6 +- .../src/meeting_bot/client.py | 28 +- .../src/meeting_bot/schemas.py | 19 +- .../src/question_generation/__init__.py | 4 + .../src/question_generation/generator.py | 32 +- .../question_generation/question_parsing.py | 24 ++ .../src/screening_pipeline/__init__.py | 22 +- .../screening_pipeline/evaluation_builders.py | 90 ++++++ .../src/screening_pipeline/evaluator.py | 194 ++++-------- .../src/screening_pipeline/orchestrator.py | 224 +++++++------ .../src/screening_pipeline/persistence.py | 48 +++ .../src/screening_pipeline/prompt_builder.py | 59 ++++ .../src/screening_pipeline/session_api.py | 36 +-- .../src/screening_pipeline/speech_filter.py | 28 ++ .../screening_pipeline/summary_calculator.py | 43 +++ .../tests/api/v1/test_meeting_bot.py | 113 +++++++ .../tests/api/v1/test_parsing.py | 59 ++++ .../tests/api/v1/test_question_generation.py | 70 +++++ services/ai-core-services/tests/conftest.py | 33 +- .../tests/meeting_bot/__init__.py | 0 .../tests/meeting_bot/test_client.py | 28 ++ .../tests/question_generation/__init__.py | 0 .../question_generation/test_generator.py | 54 ++++ .../question_generation/test_match_context.py | 19 ++ .../test_question_parsing.py | 45 +++ .../tests/screening_pipeline/__init__.py | 0 .../test_evaluator_helpers.py | 93 ++++++ .../screening_pipeline/test_orchestrator.py | 28 ++ .../screening_pipeline/test_persistence.py | 51 +++ .../test_summary_calculator.py | 27 ++ .../test_webhook_handler.py | 40 +++ services/ai-core-services/uv.lock | 293 +++++++++++++++++- 46 files changed, 1602 insertions(+), 390 deletions(-) rename services/ai-core-services/src/api/{v1/.gitkeep => __init__.py} (100%) rename services/ai-core-services/src/{interview_analysis/.gitkeep => api/v1/__init__.py} (100%) create mode 100644 services/ai-core-services/src/common/__init__.py create mode 100644 services/ai-core-services/src/core/__init__.py create mode 100644 services/ai-core-services/src/interview_analysis/__init__.py create mode 100644 services/ai-core-services/src/question_generation/question_parsing.py create mode 100644 services/ai-core-services/src/screening_pipeline/evaluation_builders.py create mode 100644 services/ai-core-services/src/screening_pipeline/persistence.py create mode 100644 services/ai-core-services/src/screening_pipeline/prompt_builder.py create mode 100644 services/ai-core-services/src/screening_pipeline/speech_filter.py create mode 100644 services/ai-core-services/src/screening_pipeline/summary_calculator.py create mode 100644 services/ai-core-services/tests/api/v1/test_meeting_bot.py create mode 100644 services/ai-core-services/tests/api/v1/test_parsing.py create mode 100644 services/ai-core-services/tests/api/v1/test_question_generation.py create mode 100644 services/ai-core-services/tests/meeting_bot/__init__.py create mode 100644 services/ai-core-services/tests/meeting_bot/test_client.py create mode 100644 services/ai-core-services/tests/question_generation/__init__.py create mode 100644 services/ai-core-services/tests/question_generation/test_generator.py create mode 100644 services/ai-core-services/tests/question_generation/test_match_context.py create mode 100644 services/ai-core-services/tests/question_generation/test_question_parsing.py create mode 100644 services/ai-core-services/tests/screening_pipeline/__init__.py create mode 100644 services/ai-core-services/tests/screening_pipeline/test_evaluator_helpers.py create mode 100644 services/ai-core-services/tests/screening_pipeline/test_orchestrator.py create mode 100644 services/ai-core-services/tests/screening_pipeline/test_persistence.py create mode 100644 services/ai-core-services/tests/screening_pipeline/test_summary_calculator.py create mode 100644 services/ai-core-services/tests/screening_pipeline/test_webhook_handler.py diff --git a/.gitignore b/.gitignore index 0121c81..b26fab3 100644 --- a/.gitignore +++ b/.gitignore @@ -155,6 +155,8 @@ activemq-data/ # Local-only apps/core-api/scripts +apps/core-api/secrets/ +apps/core-api/.venv-oauth/ # Local platform settings file (Super Admin editable defaults) apps/core-api/data/ diff --git a/apps/frontend/.env.example b/apps/frontend/.env.example index 11bbb87..393032e 100644 --- a/apps/frontend/.env.example +++ b/apps/frontend/.env.example @@ -4,6 +4,11 @@ # Leave empty so the browser calls /api on the same host (Vite proxy or Nginx). VITE_CORE_API_URL= +# Vite /api proxy target (server-side only; not VITE_*). +# Native: http://127.0.0.1:8000 (default) +# Docker Compose sets this automatically to http://core-api:8000 +# DEV_API_PROXY_TARGET=http://127.0.0.1:8000 + # Optional WebSocket screening URL (if used). # VITE_AI_SCREENING_URL=ws://localhost:8002 diff --git a/apps/frontend/vite.config.js b/apps/frontend/vite.config.js index f87df94..3ad29dc 100644 --- a/apps/frontend/vite.config.js +++ b/apps/frontend/vite.config.js @@ -1,17 +1,24 @@ -import { defineConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' -export default defineConfig({ - plugins: [react()], - server: { - port: 5173, - host: true, - allowedHosts: true, - proxy: { - '/api': { - target: 'http://127.0.0.1:8000', - changeOrigin: true, +export default defineConfig(({ mode }) => { + // Native: 127.0.0.1:8000. Docker Compose: set DEV_API_PROXY_TARGET=http://core-api:8000 + const env = loadEnv(mode, process.cwd(), '') + const apiTarget = + env.DEV_API_PROXY_TARGET || process.env.DEV_API_PROXY_TARGET || 'http://127.0.0.1:8000' + + return { + plugins: [react()], + server: { + port: 5173, + host: true, + allowedHosts: true, + proxy: { + '/api': { + target: apiTarget, + changeOrigin: true, + }, }, }, - }, + } }) diff --git a/docker-compose.yml b/docker-compose.yml index 3c5571e..6dab4cf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -106,6 +106,9 @@ services: - "5173:5173" env_file: - ./apps/frontend/.env + environment: + # Vite proxy runs inside this container — use Docker DNS, not localhost + DEV_API_PROXY_TARGET: http://core-api:8000 depends_on: - core-api - ai-core-services diff --git a/services/ai-core-services/.agents/AGENTS.md b/services/ai-core-services/.agents/AGENTS.md index f8211ce..9a6fc6e 100644 --- a/services/ai-core-services/.agents/AGENTS.md +++ b/services/ai-core-services/.agents/AGENTS.md @@ -23,20 +23,26 @@ src/ ├── job_fit_analysis/ # Resume-JD matching & scoring ├── question_generation/ # Interview question generation ├── meeting_bot/ # Attendee.dev integration -├── screening_pipeline/ # STT/TTS (planned) +├── screening_pipeline/ # Live interview: STT/TTS, evaluator, orchestrator └── interview_analysis/ # Post-call evaluation (planned) ``` ## Layering rules 1. **API layer** (`api/v1/`) — request/response only; no LLM prompts or business formulas -2. **Domain layer** — orchestration (`matcher.py`, parsers); accepts injected clients -3. **Pure logic** — `score_calculator.py`, schema validation; no I/O, fully unit-testable +2. **Domain layer** — orchestration (`matcher.py`, parsers, `InterviewOrchestrator`); accepts injected clients +3. **Pure logic** — `score_calculator.py`, `experience_calculator.py`, `speech_filter.py`; no I/O 4. **Prompts** — `prompt_builder.py` per domain; large templates stay out of orchestrators +## API error pattern + +Domain failures return HTTP 200 with `{ "status": "error", "error_message": "..." }` (parsing, matching, question generation, meeting bot). Prefer this over `HTTPException` for business/domain errors. Delete-bot success remains `204`; delete failures use the structured error body. + ## Testing strategy -- `tests/job_fit_analysis/test_score_calculator.py` — deterministic scoring math -- `tests/parsing/test_schemas.py` — Pydantic model validation +- `tests/job_fit_analysis/` — deterministic scoring math +- `tests/parsing/` — schemas + experience calculator +- `tests/question_generation/` — match context, question parsing, generator with mocked LLM +- `tests/screening_pipeline/` — evaluator builders, summary calculator, persistence, webhook, orchestrator mocks - `tests/api/v1/` — route tests with mocked domain services - Never hit real Ollama/MinIO/Attendee in unit tests diff --git a/services/ai-core-services/README.md b/services/ai-core-services/README.md index 8d208ed..27bb504 100644 --- a/services/ai-core-services/README.md +++ b/services/ai-core-services/README.md @@ -79,8 +79,12 @@ All endpoints are private inter-service APIs (`/internal/v1/*`) invoked internal | **Parsing** | `/internal/v1/parse/resume` | `POST` | Parses candidate resume binary $\rightarrow$ `parsed_resume` JSON. | | **Matching** | `/internal/v1/match/resume-jd` | `POST` | Calculates `resume_score` (0-100) & `matching_result` JSONB. | | **Question Gen** | `/internal/v1/screening/questions/generate` | `POST` | Auto-generates static `generated_questions` array. | -| **Meeting Bot** | `/internal/v1/screening/bot/dispatch` | `POST` | Dispatches Attendee meeting bot to Google Meet URL. | -| **Interview Analysis** | `/internal/v1/screening/analysis/evaluate` | `POST` | Evaluates call transcript via `gemma4:31b` $\rightarrow$ `interview_analysis`. | +| **Meeting Bot** | `/screening/bot/dispatch` | `POST` | Dispatches Attendee meeting bot to Google Meet URL. | +| **Meeting Bot** | `/screening/bot/{bot_id}` | `GET` | Fetch bot status. | +| **Meeting Bot** | `/screening/bot/{bot_id}/leave` | `POST` | Instruct bot to leave the meeting. | +| **Meeting Bot** | `/screening/bot/{bot_id}` | `DELETE` | Delete bot record (204 on success). | +| **Live Screening** | `/screening/webhook` | `POST` | Attendee.dev lifecycle / participant webhooks. | +| **Live Screening** | `/attendee-websocket/{session_id}` | `WS` | Dual-channel audio stream for STT/TTS interview. | --- diff --git a/services/ai-core-services/src/api/v1/.gitkeep b/services/ai-core-services/src/api/__init__.py similarity index 100% rename from services/ai-core-services/src/api/v1/.gitkeep rename to services/ai-core-services/src/api/__init__.py diff --git a/services/ai-core-services/src/interview_analysis/.gitkeep b/services/ai-core-services/src/api/v1/__init__.py similarity index 100% rename from services/ai-core-services/src/interview_analysis/.gitkeep rename to services/ai-core-services/src/api/v1/__init__.py diff --git a/services/ai-core-services/src/api/v1/meeting_bot.py b/services/ai-core-services/src/api/v1/meeting_bot.py index cbbc666..73837d6 100644 --- a/services/ai-core-services/src/api/v1/meeting_bot.py +++ b/services/ai-core-services/src/api/v1/meeting_bot.py @@ -1,6 +1,13 @@ -from fastapi import APIRouter, HTTPException, status, Response -from src.meeting_bot.schemas import DispatchBotRequest, DispatchBotResponse, BotStatusResponse, LeaveBotResponse +from fastapi import APIRouter, status, Response + from src.meeting_bot.client import bot_client +from src.meeting_bot.schemas import ( + BotStatusResponse, + DeleteBotResponse, + DispatchBotRequest, + DispatchBotResponse, + LeaveBotResponse, +) router = APIRouter(prefix="/screening/bot", tags=["Meeting Bot Service"]) @@ -11,9 +18,10 @@ async def dispatch_bot(request: DispatchBotRequest): try: return await bot_client.dispatch_bot(request) except Exception as err: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Failed to dispatch meeting bot: {str(err)}" + return DispatchBotResponse( + interview_session_id=request.interview_session_id, + status="error", + error_message=str(err), ) @@ -23,9 +31,10 @@ async def get_bot_status(bot_id: str): try: return await bot_client.get_bot_status(bot_id) except Exception as err: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Failed to fetch bot status: {str(err)}" + return BotStatusResponse( + bot_id=bot_id, + status="error", + error_message=str(err), ) @@ -35,25 +44,35 @@ async def leave_bot(bot_id: str): try: return await bot_client.leave_bot(bot_id) except Exception as err: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Failed to instruct bot to leave: {str(err)}" + return LeaveBotResponse( + bot_id=bot_id, + status="error", + error_message=str(err), ) -@router.delete("/{bot_id}", status_code=status.HTTP_204_NO_CONTENT) +@router.delete( + "/{bot_id}", + response_model=None, + responses={ + 204: {"description": "Bot deleted"}, + 200: {"model": DeleteBotResponse, "description": "Delete failed"}, + }, +) async def delete_bot(bot_id: str): - """Delete a bot record from Attendee.""" + """Delete a bot record from Attendee. Success is 204; domain failures return JSON.""" try: success = await bot_client.delete_bot(bot_id) if not success: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Failed to delete bot" + return DeleteBotResponse( + bot_id=bot_id, + status="error", + error_message="Failed to delete bot", ) return Response(status_code=status.HTTP_204_NO_CONTENT) except Exception as err: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Failed to delete bot: {str(err)}" + return DeleteBotResponse( + bot_id=bot_id, + status="error", + error_message=str(err), ) diff --git a/services/ai-core-services/src/api/v1/parsing.py b/services/ai-core-services/src/api/v1/parsing.py index ee0fc32..acdd6c3 100644 --- a/services/ai-core-services/src/api/v1/parsing.py +++ b/services/ai-core-services/src/api/v1/parsing.py @@ -1,56 +1,56 @@ -import asyncio -from fastapi import APIRouter, HTTPException -from src.parsing.schemas import ParseResumeRequest, ParsedResumeResponse, RawJDRequest, ParsedJDResponse -from src.parsing.resume_parser import resume_parser -from src.parsing.jd_parser import jd_parser +import json + +from fastapi import APIRouter + from src.core.logger import logger +from src.parsing.jd_parser import jd_parser +from src.parsing.resume_parser import resume_parser +from src.parsing.schemas import ( + ParseResumeRequest, + ParsedJDResponse, + ParsedResumeResponse, + RawJDRequest, +) router = APIRouter(tags=["Parsing"]) + @router.post("/resume", response_model=ParsedResumeResponse) async def parse_resume_endpoint(request: ParseResumeRequest): - """ - Parses a single resume. - """ + """Parses a single resume.""" logger.info(f"Received parse request for resume: {request.resume_name}") try: - parsed_json = await resume_parser.parse_s3_file( - s3_key=request.resume_path - ) + parsed_json = await resume_parser.parse_s3_file(s3_key=request.resume_path) return { "resume_name": request.resume_name, "status": "success", - "parsed_resume": parsed_json + "parsed_resume": parsed_json, } except Exception as e: logger.error(f"Error parsing resume {request.resume_path}: {e}") return { "resume_name": request.resume_name, "status": "error", - "error_message": str(e) + "error_message": str(e), } + @router.post("/jd", response_model=ParsedJDResponse) async def parse_jd_endpoint(request: RawJDRequest): - """ - Parses a single JD from a raw JSON payload. - """ - logger.info(f"Received parse request for JD.") - import json - + """Parses a single JD from a raw JSON payload.""" + logger.info("Received parse request for JD.") + try: - # Convert the incoming JSON object into a string for the LLM jd_text = json.dumps(request.model_dump(), indent=2) parsed_json = await jd_parser.parse_jd_text(jd_text) - return { "status": "success", - "parsed_jd": parsed_json + "parsed_jd": parsed_json, } except Exception as e: logger.error(f"Error parsing JD: {e}") return { "status": "error", - "error_message": str(e) + "error_message": str(e), } diff --git a/services/ai-core-services/src/common/__init__.py b/services/ai-core-services/src/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/src/core/__init__.py b/services/ai-core-services/src/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/src/interview_analysis/__init__.py b/services/ai-core-services/src/interview_analysis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/src/meeting_bot/__init__.py b/services/ai-core-services/src/meeting_bot/__init__.py index a5a4191..e054435 100644 --- a/services/ai-core-services/src/meeting_bot/__init__.py +++ b/services/ai-core-services/src/meeting_bot/__init__.py @@ -1,9 +1,7 @@ -from src.meeting_bot.attendee import attendee_client, AttendeeApiClient -from src.meeting_bot.client import bot_client, AttendeeBotClient +"""Meeting bot domain package. -__all__ = [ - "attendee_client", - "AttendeeApiClient", - "bot_client", - "AttendeeBotClient", -] +Import clients from `src.meeting_bot.client` / `src.meeting_bot.attendee` +to avoid loading settings on schema-only imports. +""" + +__all__: list[str] = [] diff --git a/services/ai-core-services/src/meeting_bot/attendee.py b/services/ai-core-services/src/meeting_bot/attendee.py index 40d428c..a6776fd 100644 --- a/services/ai-core-services/src/meeting_bot/attendee.py +++ b/services/ai-core-services/src/meeting_bot/attendee.py @@ -135,7 +135,11 @@ async def leave_bot(self, bot_id: str) -> LeaveBotResponse: except Exception as err: logger.error("Error communicating with Attendee API for leave_bot", extra={"error": str(err)}) - return LeaveBotResponse(bot_id=bot_id, status="error") + return LeaveBotResponse( + bot_id=bot_id, + status="error", + error_message="Failed to instruct bot to leave", + ) async def delete_bot(self, bot_id: str) -> bool: """Delete the bot record from Attendee.""" diff --git a/services/ai-core-services/src/meeting_bot/client.py b/services/ai-core-services/src/meeting_bot/client.py index c41c423..bc8fca8 100644 --- a/services/ai-core-services/src/meeting_bot/client.py +++ b/services/ai-core-services/src/meeting_bot/client.py @@ -1,4 +1,3 @@ -from fastapi import HTTPException from datetime import datetime, timezone, timedelta from src.core.logger import logger from src.core.config import settings @@ -21,35 +20,34 @@ class AttendeeBotClient: async def dispatch_bot(self, request: DispatchBotRequest) -> DispatchBotResponse: """Schedule an Attendee meeting bot for the specified interview session's scheduled_at time.""" session_detail = await interview_session_repo.get_by_id(request.interview_session_id) - + meeting_url = request.meeting_url scheduled_at = None if session_detail: scheduled_at = session_detail.scheduled_at if scheduled_at: try: - # The calendar saves IST but marks it as UTC. + # The calendar saves IST but marks it as UTC. # We subtract 5:30 to get the TRUE UTC time for Attendee.dev dt = datetime.fromisoformat(scheduled_at.replace("Z", "+00:00")) dt = dt - timedelta(hours=5, minutes=30) - - # If it's already past the start time, block it and return an error to the user + if dt < datetime.now(timezone.utc): - raise HTTPException(status_code=400, detail="Cannot dispatch bot. The scheduled interview time is in the past.") - else: - scheduled_at = dt.isoformat() - except HTTPException: - raise # Re-raise the 400 error so Postman sees it + raise ValueError( + "Cannot dispatch bot. The scheduled interview time is in the past." + ) + scheduled_at = dt.isoformat() + except ValueError: + raise except Exception as e: logger.warning(f"Failed to adjust scheduled_at for IST: {e}") - + if not meeting_url and session_detail.comment: meeting_url = session_detail.comment if not meeting_url: meeting_url = "https://meet.google.com/ezs-screener-demo" - # Construct strongly-typed AttendeeScheduleBotRequest attendee_req = AttendeeScheduleBotRequest( meeting_url=meeting_url, bot_name="ezscreener", @@ -57,7 +55,7 @@ async def dispatch_bot(self, request: DispatchBotRequest) -> DispatchBotResponse transcription_settings={ "meeting_closed_captions": {} }, - websocket_settings=AttendeeWebsocketSettings( + websocket_settings=AttendeeWebsocketSettings( audio=AttendeeAudioSettings( url=f"{settings.websocket_url.rstrip('/')}/{request.interview_session_id}", sample_rate=24000 @@ -65,11 +63,9 @@ async def dispatch_bot(self, request: DispatchBotRequest) -> DispatchBotResponse ) ) - # Execute external Attendee API call using typed request & response attendee_res = await attendee_client.schedule_bot(attendee_req) - dispatched_at = datetime.now(timezone.utc).isoformat() - + return DispatchBotResponse( bot_id=attendee_res.id, interview_session_id=request.interview_session_id, diff --git a/services/ai-core-services/src/meeting_bot/schemas.py b/services/ai-core-services/src/meeting_bot/schemas.py index 6dae324..54d3148 100644 --- a/services/ai-core-services/src/meeting_bot/schemas.py +++ b/services/ai-core-services/src/meeting_bot/schemas.py @@ -28,6 +28,13 @@ class AttendeeScheduleBotRequest(BaseModel): class LeaveBotResponse(BaseModel): bot_id: str status: str = "leaving" + error_message: Optional[str] = None + + +class DeleteBotResponse(BaseModel): + bot_id: str + status: str = "deleted" + error_message: Optional[str] = None class AttendeeScheduleBotResponse(BaseModel): @@ -62,16 +69,18 @@ class InterviewSessionDetailResponse(BaseModel): class DispatchBotResponse(BaseModel): - bot_id: str + bot_id: Optional[str] = None interview_session_id: str status: str = "scheduled" - meeting_url: str + meeting_url: Optional[str] = None scheduled_at: Optional[str] = None - dispatched_at: str + dispatched_at: Optional[str] = None + error_message: Optional[str] = None class BotStatusResponse(BaseModel): - bot_id: str + bot_id: Optional[str] = None status: str - meeting_url: str + meeting_url: Optional[str] = None duration_seconds: Optional[int] = None + error_message: Optional[str] = None diff --git a/services/ai-core-services/src/question_generation/__init__.py b/services/ai-core-services/src/question_generation/__init__.py index a8502b7..7265f36 100644 --- a/services/ai-core-services/src/question_generation/__init__.py +++ b/services/ai-core-services/src/question_generation/__init__.py @@ -1,3 +1,5 @@ +from src.question_generation.match_context import neutral_job_fit_analysis +from src.question_generation.question_parsing import parse_questions from src.question_generation.schemas import ( GeneratedQuestion, GenerateQuestionsRequest, @@ -8,4 +10,6 @@ "GeneratedQuestion", "GenerateQuestionsRequest", "GenerateQuestionsResponse", + "neutral_job_fit_analysis", + "parse_questions", ] diff --git a/services/ai-core-services/src/question_generation/generator.py b/services/ai-core-services/src/question_generation/generator.py index 7843c67..4e1e8c7 100644 --- a/services/ai-core-services/src/question_generation/generator.py +++ b/services/ai-core-services/src/question_generation/generator.py @@ -1,45 +1,31 @@ import json -from typing import Any +from typing import Any, Optional -from src.llm.client import OllamaClient +from src.common.llm_utils import parse_llm_json from src.core.logger import logger +from src.llm.client import OllamaClient +from src.question_generation.match_context import neutral_job_fit_analysis +from src.question_generation.prompt_builder import question_prompt_builder +from src.question_generation.question_parsing import parse_questions from src.question_generation.schemas import ( GenerateQuestionsRequest, GenerateQuestionsResponse, GeneratedQuestion, ) -from src.question_generation.prompt_builder import question_prompt_builder -from src.question_generation.match_context import neutral_job_fit_analysis -from src.common.llm_utils import parse_llm_json - - -def _parse_questions(raw: Any) -> list[GeneratedQuestion]: - parsed_questions = parse_llm_json(raw) if isinstance(raw, str) else raw - - if isinstance(parsed_questions, dict): - for key in ("questions", "generated_questions", "data"): - if key in parsed_questions and isinstance(parsed_questions[key], list): - parsed_questions = parsed_questions[key] - break - - if not isinstance(parsed_questions, list): - raise ValueError(f"Expected JSON array, got {type(parsed_questions).__name__}") - - return [GeneratedQuestion(**q) for q in parsed_questions] class QuestionGenerator: """Orchestrates the question generation pipeline.""" - def __init__(self): - self.llm_client = OllamaClient() + def __init__(self, llm_client: Optional[OllamaClient] = None): + self.llm_client = llm_client or OllamaClient() async def _call_llm(self, prompt: str, *, log_extra: dict[str, Any]) -> list[GeneratedQuestion]: logger.info("Sending question generation prompt to LLM", extra=log_extra) response = await self.llm_client.openai_chat_generate( prompt=prompt, temperature=0.3, timeout=120.0 ) - return _parse_questions(response.response) + return parse_questions(response.response) async def generate(self, request: GenerateQuestionsRequest) -> GenerateQuestionsResponse: """Generate tailored interview questions for a candidate session or job bank.""" diff --git a/services/ai-core-services/src/question_generation/question_parsing.py b/services/ai-core-services/src/question_generation/question_parsing.py new file mode 100644 index 0000000..7f87c24 --- /dev/null +++ b/services/ai-core-services/src/question_generation/question_parsing.py @@ -0,0 +1,24 @@ +"""Pure helpers for parsing LLM question-generation output.""" + +from __future__ import annotations + +from typing import Any + +from src.common.llm_utils import parse_llm_json +from src.question_generation.schemas import GeneratedQuestion + + +def parse_questions(raw: Any) -> list[GeneratedQuestion]: + """Normalize LLM output into a list of GeneratedQuestion models.""" + parsed_questions = parse_llm_json(raw) if isinstance(raw, str) else raw + + if isinstance(parsed_questions, dict): + for key in ("questions", "generated_questions", "data"): + if key in parsed_questions and isinstance(parsed_questions[key], list): + parsed_questions = parsed_questions[key] + break + + if not isinstance(parsed_questions, list): + raise ValueError(f"Expected JSON array, got {type(parsed_questions).__name__}") + + return [GeneratedQuestion(**q) for q in parsed_questions] diff --git a/services/ai-core-services/src/screening_pipeline/__init__.py b/services/ai-core-services/src/screening_pipeline/__init__.py index 34253dd..c899f9d 100644 --- a/services/ai-core-services/src/screening_pipeline/__init__.py +++ b/services/ai-core-services/src/screening_pipeline/__init__.py @@ -1 +1,21 @@ -# Screening pipeline module +from src.screening_pipeline.evaluation_builders import ( + build_evaluation_block, + build_qa_entry, + build_skip_evaluation, +) +from src.screening_pipeline.prompt_builder import ( + ScreeningPromptBuilder, + screening_prompt_builder, +) +from src.screening_pipeline.speech_filter import is_probable_hallucination +from src.screening_pipeline.summary_calculator import compute_final_summary + +__all__ = [ + "build_evaluation_block", + "build_qa_entry", + "build_skip_evaluation", + "ScreeningPromptBuilder", + "screening_prompt_builder", + "is_probable_hallucination", + "compute_final_summary", +] diff --git a/services/ai-core-services/src/screening_pipeline/evaluation_builders.py b/services/ai-core-services/src/screening_pipeline/evaluation_builders.py new file mode 100644 index 0000000..fbbb2a6 --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/evaluation_builders.py @@ -0,0 +1,90 @@ +"""Pure builders for screening evaluation / Q&A payloads (no I/O).""" + +from __future__ import annotations + +from typing import Any, Dict, List, Optional + + +def build_skip_evaluation(question_obj: Dict, transcript: str) -> Dict[str, Any]: + """Build a 0-score evaluation for skipped questions.""" + return { + "question_id": question_obj.get("id"), + "question": question_obj.get("question", ""), + "candidate_answer": transcript, + "score": 0, + "coverage_percent": 0, + "keywords_found": [], + "keywords_missing": question_obj.get("expected_keywords", []), + "decision": "NEXT_QUESTION", + "feedback": "Candidate chose to skip this question.", + } + + +def build_evaluation_block( + question_obj: Dict, + current_q: str, + transcript: str, + primary_eval: Optional[Dict], + current_eval: Dict, + follow_ups: Optional[List[Dict]] = None, +) -> Dict[str, Any]: + """Build the final evaluation block for analysis_result.evaluations[].""" + source = primary_eval if primary_eval else current_eval + + evaluation = { + "question_id": question_obj.get("id"), + "question": current_q, + "candidate_answer": transcript, + "score": source.get("score", 0), + "coverage_percent": source.get("coverage_percent", 0), + "keywords_found": source.get("keywords_found", []), + "keywords_missing": source.get("keywords_missing", []), + "decision": source.get("decision", "NEXT_QUESTION"), + "feedback": source.get("feedback", ""), + } + + if follow_ups: + follow_up_data = [] + for fu in follow_ups: + follow_up_data.append( + { + "follow_up_question": fu.get("ai_response", ""), + "follow_up_answer": fu.get("candidate_speech", ""), + "score": current_eval.get("score", 0), + "coverage_percent": current_eval.get("coverage_percent", 0), + "keywords_found": current_eval.get("keywords_found", []), + "keywords_missing": current_eval.get("keywords_missing", []), + "decision": current_eval.get("decision", "NEXT_QUESTION"), + "feedback": current_eval.get("feedback", ""), + } + ) + evaluation["follow_ups"] = follow_up_data + + return evaluation + + +def build_qa_entry( + question_obj: Dict, + current_q: str, + transcript: str, + follow_ups: Optional[List[Dict]] = None, +) -> Dict[str, Any]: + """Build the clean Q&A entry for question_answer column.""" + qa_entry = { + "question_id": question_obj.get("id"), + "bot_speech": current_q, + "candidate_answer": transcript, + } + + if follow_ups: + qa_follow_ups = [] + for fu in follow_ups: + qa_follow_ups.append( + { + "bot_speech": fu.get("ai_response", ""), + "candidate_answer": fu.get("candidate_speech", ""), + } + ) + qa_entry["follow_ups"] = qa_follow_ups + + return qa_entry diff --git a/services/ai-core-services/src/screening_pipeline/evaluator.py b/services/ai-core-services/src/screening_pipeline/evaluator.py index 38cca9c..f08ce8f 100644 --- a/services/ai-core-services/src/screening_pipeline/evaluator.py +++ b/services/ai-core-services/src/screening_pipeline/evaluator.py @@ -2,15 +2,36 @@ Answer Evaluator for the AI Screening Pipeline. Handles intent routing, answer evaluation, and evaluation result building. """ -import json -from typing import Dict, Any, Optional, Tuple, List + +from __future__ import annotations + +from typing import Any, Dict, List, Optional, Tuple + +from src.common.llm_utils import parse_llm_json from src.core.logger import logger from src.llm.client import OllamaClient +from src.screening_pipeline.evaluation_builders import ( + build_evaluation_block, + build_qa_entry, + build_skip_evaluation, +) +from src.screening_pipeline.prompt_builder import screening_prompt_builder from src.screening_pipeline.prompts import ( - INTENT_ROUTER_SYSTEM, ANSWER_EVALUATION_SYSTEM, + INTENT_ROUTER_SYSTEM, ) +_EVAL_FAILURE_FALLBACK = { + "score": 0, + "coverage_percent": 0, + "keywords_found": [], + "keywords_missing": [], + "is_sufficient": False, + "decision": "NEXT_QUESTION", + "feedback": "Evaluation failed due to an internal error.", + "suggested_follow_up": "", +} + class AnswerEvaluator: """Evaluates candidate answers using LLM-based intent routing and scoring.""" @@ -19,20 +40,20 @@ def __init__(self, llm_client: OllamaClient): self.llm_client = llm_client async def route_intent(self, current_question: str, transcript: str) -> Tuple[str, str]: - """ - Classifies the candidate's speech into an intent. - Returns: (intent, ai_response) - """ - intent_prompt = f"Current Interview Question: {current_question}\nCandidate Speech: {transcript}" + """Classify candidate speech. Returns (intent, ai_response).""" + intent_prompt = screening_prompt_builder.build_intent_prompt( + current_question, transcript + ) try: intent_res = await self.llm_client.openai_chat_generate( prompt=intent_prompt, system=INTENT_ROUTER_SYSTEM, - temperature=0.1 + temperature=0.1, ) - raw_text = intent_res.response.replace("```json", "").replace("```", "").strip() - data = json.loads(raw_text) + data = parse_llm_json(intent_res.response) + if not isinstance(data, dict): + raise ValueError("Intent router expected a JSON object") intent = data.get("intent", "ANSWERING") ai_response = data.get("response", "") except Exception as e: @@ -49,141 +70,48 @@ async def evaluate_answer( transcript: str, expected_keywords: str, answer_depth: str, - follow_up_context: Optional[List[Dict]] = None + follow_up_context: Optional[List[Dict]] = None, ) -> Dict[str, Any]: - """ - Evaluates the candidate's answer against expected keywords and strictness. - Returns the full eval_data dict from the LLM. - """ - # Build the user prompt - full_context = "You are evaluating a candidate's answer in a FIRST SCREENING interview.\n\n" - - if follow_up_context: - full_context += "NOTE: This is a FOLLOW-UP evaluation. The candidate had an insufficient primary answer.\n\n" - - full_context += f"QUESTION: {current_question}\n" - - candidate_answer = "" - if follow_up_context: - for fu in follow_up_context: - candidate_answer += f"AI: {fu.get('ai_response', '')}\nCandidate: {fu.get('candidate_speech', '')}\n" - candidate_answer += f"Candidate Latest Answer: {transcript}" - - full_context += f"CANDIDATE ANSWER: {candidate_answer}\n" - full_context += f"EXPECTED KEYWORDS (answer should address most of these): {expected_keywords}\n" - full_context += f"EVALUATION STRICTNESS LEVEL: {answer_depth}\n" + """Evaluate answer against keywords/strictness. Returns LLM eval_data dict.""" + full_context = screening_prompt_builder.build_evaluation_prompt( + current_question=current_question, + transcript=transcript, + expected_keywords=expected_keywords, + answer_depth=answer_depth, + follow_up_context=follow_up_context, + ) try: eval_res = await self.llm_client.openai_chat_generate( prompt=full_context, system=ANSWER_EVALUATION_SYSTEM, - temperature=0.2 + temperature=0.2, ) - raw_text = eval_res.response.replace("```json", "").replace("```", "").strip() - eval_data = json.loads(raw_text) + eval_data = parse_llm_json(eval_res.response) + if not isinstance(eval_data, dict): + raise ValueError("Answer evaluation expected a JSON object") except Exception as e: logger.error("Answer evaluation failed", extra={"error": str(e)}) - eval_data = { - "score": 0, "coverage_percent": 0, - "keywords_found": [], "keywords_missing": [], - "is_sufficient": False, "decision": "NEXT_QUESTION", - "feedback": "Evaluation failed due to an internal error.", - "suggested_follow_up": "" - } + eval_data = dict(_EVAL_FAILURE_FALLBACK) decision = eval_data.get("decision", "NEXT_QUESTION") - follow_up_question = eval_data.get("suggested_follow_up", "") - if decision == "REPEAT_QUESTION": - follow_up_question = "I'm sorry, could you please repeat your answer?" + eval_data["suggested_follow_up"] = ( + "I'm sorry, could you please repeat your answer?" + ) - logger.info("Answer evaluated", extra={ - "decision": decision, - "score": eval_data.get("score"), - "keywords_missing": eval_data.get("keywords_missing", []), - }) + logger.info( + "Answer evaluated", + extra={ + "decision": decision, + "score": eval_data.get("score"), + "keywords_missing": eval_data.get("keywords_missing", []), + }, + ) return eval_data - @staticmethod - def build_skip_evaluation(question_obj: Dict, transcript: str) -> Dict[str, Any]: - """Builds a 0-score evaluation for skipped questions.""" - return { - "question_id": question_obj.get("id"), - "question": question_obj.get("question", ""), - "candidate_answer": transcript, - "score": 0, - "coverage_percent": 0, - "keywords_found": [], - "keywords_missing": question_obj.get("expected_keywords", []), - "decision": "NEXT_QUESTION", - "feedback": "Candidate chose to skip this question." - } - - @staticmethod - def build_evaluation_block( - question_obj: Dict, - current_q: str, - transcript: str, - primary_eval: Optional[Dict], - current_eval: Dict, - follow_ups: Optional[List[Dict]] = None - ) -> Dict[str, Any]: - """Builds the final evaluation block for analysis_result.evaluations[].""" - # Use primary_eval for the top-level scores if this was a follow-up completion - source = primary_eval if primary_eval else current_eval - - evaluation = { - "question_id": question_obj.get("id"), - "question": current_q, - "candidate_answer": transcript, - "score": source.get("score", 0), - "coverage_percent": source.get("coverage_percent", 0), - "keywords_found": source.get("keywords_found", []), - "keywords_missing": source.get("keywords_missing", []), - "decision": source.get("decision", "NEXT_QUESTION"), - "feedback": source.get("feedback", "") - } - - # Attach follow-up evaluation data - if follow_ups: - follow_up_data = [] - for fu in follow_ups: - follow_up_data.append({ - "follow_up_question": fu.get("ai_response", ""), - "follow_up_answer": fu.get("candidate_speech", ""), - "score": current_eval.get("score", 0), - "coverage_percent": current_eval.get("coverage_percent", 0), - "keywords_found": current_eval.get("keywords_found", []), - "keywords_missing": current_eval.get("keywords_missing", []), - "decision": current_eval.get("decision", "NEXT_QUESTION"), - "feedback": current_eval.get("feedback", "") - }) - evaluation["follow_ups"] = follow_up_data - - return evaluation - - @staticmethod - def build_qa_entry( - question_obj: Dict, - current_q: str, - transcript: str, - follow_ups: Optional[List[Dict]] = None - ) -> Dict[str, Any]: - """Builds the clean Q&A entry for question_answer column.""" - qa_entry = { - "question_id": question_obj.get("id"), - "bot_speech": current_q, - "candidate_answer": transcript - } - - if follow_ups: - qa_follow_ups = [] - for fu in follow_ups: - qa_follow_ups.append({ - "bot_speech": fu.get("ai_response", ""), - "candidate_answer": fu.get("candidate_speech", "") - }) - qa_entry["follow_ups"] = qa_follow_ups - - return qa_entry + # Keep static wrappers for existing call sites on AnswerEvaluator + build_skip_evaluation = staticmethod(build_skip_evaluation) + build_evaluation_block = staticmethod(build_evaluation_block) + build_qa_entry = staticmethod(build_qa_entry) diff --git a/services/ai-core-services/src/screening_pipeline/orchestrator.py b/services/ai-core-services/src/screening_pipeline/orchestrator.py index cbca2db..10a0b3a 100644 --- a/services/ai-core-services/src/screening_pipeline/orchestrator.py +++ b/services/ai-core-services/src/screening_pipeline/orchestrator.py @@ -2,23 +2,33 @@ Interview Orchestrator — Main state machine for the AI screening interview. Controls the flow: Greeting → Questions → Evaluation → Follow-up → Closing. """ + +from __future__ import annotations + import asyncio import random -import json -from typing import Any -from src.core.logger import logger +from typing import TYPE_CHECKING, Any, Optional + from src.core.config import settings +from src.core.logger import logger +from src.llm.client import OllamaClient from src.meeting_bot.repository import interview_session_repo -from src.screening_pipeline.stt_client import WhisperCloudSTTClient -from src.screening_pipeline.tts_client import LocalKokoroTTSClient from src.screening_pipeline.evaluator import AnswerEvaluator -from src.screening_pipeline.session_api import SessionApiClient +from src.screening_pipeline.persistence import ( + persist_completed_question, + persist_interview_close, +) from src.screening_pipeline.prompts import ( - GREETING_TEXT, CLOSING_TEXT, + GREETING_TEXT, MAX_FOLLOW_UPS_PER_QUESTION, ) -from src.llm.client import OllamaClient +from src.screening_pipeline.session_api import SessionApiClient +from src.screening_pipeline.speech_filter import is_probable_hallucination +from src.screening_pipeline.tts_client import LocalKokoroTTSClient + +if TYPE_CHECKING: + from src.screening_pipeline.stt_client import WhisperCloudSTTClient class InterviewOrchestrator: @@ -27,7 +37,17 @@ class InterviewOrchestrator: Coordinates STT, Intent Router, LLM Evaluator, TTS, and Core-API persistence. """ - def __init__(self, session_id: str, websocket): + def __init__( + self, + session_id: str, + websocket, + *, + stt_client: Optional[WhisperCloudSTTClient] = None, + tts_client: Optional[LocalKokoroTTSClient] = None, + evaluator: Optional[AnswerEvaluator] = None, + llm_client: Optional[OllamaClient] = None, + api_client: Optional[SessionApiClient] = None, + ): self.session_id = session_id self.websocket = websocket self.session: Any = None @@ -35,20 +55,22 @@ def __init__(self, session_id: str, websocket): self.current_question_idx = 0 self.is_active = False - # Audio clients - self.stt_client = WhisperCloudSTTClient( - api_url=settings.whisper_api_url, - api_key=settings.whisper_api_key, - on_transcript=self.handle_candidate_speech - ) - self.tts_client = LocalKokoroTTSClient() + if stt_client is not None: + self.stt_client = stt_client + else: + from src.screening_pipeline.stt_client import WhisperCloudSTTClient as _STT + + self.stt_client = _STT( + api_url=settings.whisper_api_url, + api_key=settings.whisper_api_key, + on_transcript=self.handle_candidate_speech, + ) + self.tts_client = tts_client or LocalKokoroTTSClient() - # AI modules - llm_client = OllamaClient() - self.evaluator = AnswerEvaluator(llm_client) - self.api_client: SessionApiClient = None # Initialized after session is loaded + resolved_llm = llm_client or OllamaClient() + self.evaluator = evaluator or AnswerEvaluator(resolved_llm) + self.api_client = api_client # Usually set after session load - # State tracking self.current_interaction_state = "idle" # idle, speaking, listening, evaluating self.transcript_log: list = [] self.analysis_evaluations: list = [] @@ -58,31 +80,33 @@ def __init__(self, session_id: str, websocket): async def start(self): """Initializes the interview session and speaks the greeting.""" logger.info("Orchestrator starting", extra={"session_id": self.session_id}) - - # The websocket path parameter (self.session_id) actually contains the interview_session_id - # because the true bot_id isn't known at the time the websocket URL is generated. + + # Path param is interview_session_id (bot_id unknown when WS URL is built). self.session = await interview_session_repo.get_by_id(self.session_id) if not self.session: - logger.error("No session found for bot. Cannot start interview.", extra={"session_id": self.session_id}) + logger.error( + "No session found for bot. Cannot start interview.", + extra={"session_id": self.session_id}, + ) return - # Initialize the API client with the real session ID - self.api_client = SessionApiClient(session_id=str(self.session.id)) + if self.api_client is None: + self.api_client = SessionApiClient(session_id=str(self.session.id)) - # Fetch questions and randomize their order self.questions = self.session.generated_questions or [] random.shuffle(self.questions) self.is_active = True await self.stt_client.connect() - # Greeting - self.transcript_log.append({ - "interaction_type": "greeting", - "bot_speech": GREETING_TEXT, - "candidate_answer": "" - }) + self.transcript_log.append( + { + "interaction_type": "greeting", + "bot_speech": GREETING_TEXT, + "candidate_answer": "", + } + ) await self.speak(GREETING_TEXT) self.current_interaction_state = "listening" @@ -97,20 +121,12 @@ def handle_candidate_speech(self, transcript: str): """Callback from STT when the candidate finishes speaking.""" if not self.is_active or self.current_interaction_state != "listening": return - - # Ignore common Whisper hallucinations caused by background noise or silence - cleaned = transcript.strip().lower() - import re - cleaned = re.sub(r'[^\w\s]', '', cleaned) - - hallucinations = { - "thank you", "thanks", "you", "okay", "ok", "yeah", - "thank you for watching", "thanks for watching", "subscribe", - "no i dont know", "okay i dont know", "i dont know" - } - - if cleaned in hallucinations or len(cleaned) < 2: - logger.info("Ignored probable Whisper hallucination or noise", extra={"transcript": transcript}) + + if is_probable_hallucination(transcript): + logger.info( + "Ignored probable Whisper hallucination or noise", + extra={"transcript": transcript}, + ) return logger.info("Candidate speech received", extra={"transcript": transcript}) @@ -121,8 +137,6 @@ def handle_candidate_speech(self, transcript: str): async def _process_speech(self, transcript: str): """Routes the candidate's speech through intent detection and evaluation.""" - - # If candidate is responding to the greeting, just move to first question if self.transcript_log and self.transcript_log[-1].get("interaction_type") == "greeting": self.transcript_log[-1]["candidate_answer"] = transcript await self._ask_next_question() @@ -131,7 +145,6 @@ async def _process_speech(self, transcript: str): question_obj = self.questions[self.current_question_idx] current_q = question_obj.get("question", "") - # ── Step 1: Intent Routing ── intent, ai_response = await self.evaluator.route_intent(current_q, transcript) if intent in ["CLARIFICATION", "SMALL_TALK"]: @@ -142,7 +155,6 @@ async def _process_speech(self, transcript: str): await self._handle_skip(question_obj, current_q, transcript) return - # ── Step 2: Answer Evaluation ── await self._handle_answer(question_obj, current_q, transcript) # ──────────────────────────── INTENT HANDLERS ──────────────────────────── @@ -153,10 +165,12 @@ async def _handle_conversational(self, transcript: str, ai_response: str): ai_response = "Okay, sounds good." if self.transcript_log: - self.transcript_log[-1].setdefault("follow_ups", []).append({ - "candidate_speech": transcript, - "ai_response": ai_response - }) + self.transcript_log[-1].setdefault("follow_ups", []).append( + { + "candidate_speech": transcript, + "ai_response": ai_response, + } + ) await self.speak(ai_response) self.current_interaction_state = "listening" @@ -166,11 +180,9 @@ async def _handle_skip(self, question_obj: dict, current_q: str, transcript: str if self.transcript_log: self.transcript_log[-1]["candidate_answer"] = transcript - # Save clean Q&A transcript qa_entry = AnswerEvaluator.build_qa_entry(question_obj, current_q, transcript) await self.api_client.save_transcript(qa_entry) - # Save 0-score evaluation skip_eval = AnswerEvaluator.build_skip_evaluation(question_obj, transcript) self.analysis_evaluations.append(skip_eval) await self.api_client.save_evaluation(skip_eval) @@ -180,8 +192,6 @@ async def _handle_skip(self, question_obj: dict, current_q: str, transcript: str async def _handle_answer(self, question_obj: dict, current_q: str, transcript: str): """Handles ANSWERING intent — evaluates and decides follow-up or next question.""" - - # Determine keywords: use missing keywords from primary eval for follow-up primary_eval_data = None if self.transcript_log and self.transcript_log[-1].get("primary_eval"): primary_eval_data = self.transcript_log[-1]["primary_eval"] @@ -191,52 +201,57 @@ async def _handle_answer(self, question_obj: dict, current_q: str, transcript: s expected_keywords = ", ".join(question_obj.get("expected_keywords", [])) answer_depth = question_obj.get("answer_depth", "partial_depth") - # Get follow-up context if exists follow_up_context = None if self.transcript_log and self.transcript_log[-1].get("follow_ups"): follow_up_context = self.transcript_log[-1]["follow_ups"] - # Evaluate answer via LLM eval_data = await self.evaluator.evaluate_answer( current_question=current_q, transcript=transcript, expected_keywords=expected_keywords, answer_depth=answer_depth, - follow_up_context=follow_up_context + follow_up_context=follow_up_context, ) decision = eval_data.get("decision", "NEXT_QUESTION") - is_complete = (decision == "NEXT_QUESTION") + is_complete = decision == "NEXT_QUESTION" follow_up_question = eval_data.get("suggested_follow_up", "") if decision == "REPEAT_QUESTION": is_complete = False follow_up_question = "I'm sorry, could you please repeat your answer?" - # Enforce follow-up limit - current_follow_ups = self.transcript_log[-1].get("follow_ups", []) if self.transcript_log else [] + current_follow_ups = ( + self.transcript_log[-1].get("follow_ups", []) if self.transcript_log else [] + ) if len(current_follow_ups) >= MAX_FOLLOW_UPS_PER_QUESTION and not is_complete: logger.info("Follow-up limit reached, forcing completion") is_complete = True follow_up_question = "" if not is_complete and follow_up_question: - # Ask follow-up question if self.transcript_log: self.transcript_log[-1]["primary_eval"] = eval_data - self.transcript_log[-1].setdefault("follow_ups", []).append({ - "candidate_speech": transcript, - "ai_response": follow_up_question - }) + self.transcript_log[-1].setdefault("follow_ups", []).append( + { + "candidate_speech": transcript, + "ai_response": follow_up_question, + } + ) await self.speak(follow_up_question) self.current_interaction_state = "listening" else: - # Question complete — persist to DB - await self._complete_question(question_obj, current_q, transcript, primary_eval_data, eval_data) + await self._complete_question( + question_obj, current_q, transcript, primary_eval_data, eval_data + ) async def _complete_question( - self, question_obj: dict, current_q: str, transcript: str, - primary_eval: dict, current_eval: dict + self, + question_obj: dict, + current_q: str, + transcript: str, + primary_eval: dict, + current_eval: dict, ): """Saves the completed question's transcript and evaluation to core-api.""" if self.transcript_log: @@ -244,17 +259,16 @@ async def _complete_question( follow_ups = self.transcript_log[-1].get("follow_ups") if self.transcript_log else None - # Save clean Q&A transcript (with nested follow-ups) - qa_entry = AnswerEvaluator.build_qa_entry(question_obj, current_q, transcript, follow_ups) - await self.api_client.save_transcript(qa_entry) - - # Save evaluation (with nested follow-up scores) - evaluation = AnswerEvaluator.build_evaluation_block( - question_obj, current_q, transcript, - primary_eval, current_eval, follow_ups + await persist_completed_question( + self.api_client, + self.analysis_evaluations, + question_obj=question_obj, + current_q=current_q, + transcript=transcript, + primary_eval=primary_eval, + current_eval=current_eval, + follow_ups=follow_ups, ) - self.analysis_evaluations.append(evaluation) - await self.api_client.save_evaluation(evaluation) self.current_question_idx += 1 await self._ask_next_question() @@ -270,30 +284,34 @@ async def _ask_next_question(self): question_obj = self.questions[self.current_question_idx] q_text = question_obj.get("question", "") - self.transcript_log.append({ - "interaction_type": "question", - "question_id": question_obj.get("id"), - "bot_speech": q_text, - "candidate_answer": "", - "follow_ups": [] - }) + self.transcript_log.append( + { + "interaction_type": "question", + "question_id": question_obj.get("id"), + "bot_speech": q_text, + "candidate_answer": "", + "follow_ups": [], + } + ) await self.speak(q_text) self.current_interaction_state = "listening" async def _close_interview(self): """Speaks closing message and saves the final summary.""" - self.transcript_log.append({ - "interaction_type": "closing", - "bot_speech": CLOSING_TEXT, - "candidate_answer": "" - }) - - # Calculate and persist final_summary - await self.api_client.save_final_summary(self.analysis_evaluations) - - # Persist the complete conversational transcript - await self.api_client.save_interview_metadata(self.transcript_log) + self.transcript_log.append( + { + "interaction_type": "closing", + "bot_speech": CLOSING_TEXT, + "candidate_answer": "", + } + ) + + await persist_interview_close( + self.api_client, + self.analysis_evaluations, + self.transcript_log, + ) await self.speak(CLOSING_TEXT) self.current_interaction_state = "closing" diff --git a/services/ai-core-services/src/screening_pipeline/persistence.py b/services/ai-core-services/src/screening_pipeline/persistence.py new file mode 100644 index 0000000..6768ec7 --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/persistence.py @@ -0,0 +1,48 @@ +"""Persistence helpers for completed questions and interview close (no websocket I/O).""" + +from __future__ import annotations + +from typing import Any, Dict, List, Optional + +from src.screening_pipeline.evaluator import AnswerEvaluator +from src.screening_pipeline.session_api import SessionApiClient + + +async def persist_completed_question( + api_client: SessionApiClient, + analysis_evaluations: List[Dict[str, Any]], + *, + question_obj: dict, + current_q: str, + transcript: str, + primary_eval: dict, + current_eval: dict, + follow_ups: Optional[list], +) -> Dict[str, Any]: + """Save Q&A transcript + evaluation block; append evaluation to in-memory list.""" + qa_entry = AnswerEvaluator.build_qa_entry( + question_obj, current_q, transcript, follow_ups + ) + await api_client.save_transcript(qa_entry) + + evaluation = AnswerEvaluator.build_evaluation_block( + question_obj, + current_q, + transcript, + primary_eval, + current_eval, + follow_ups, + ) + analysis_evaluations.append(evaluation) + await api_client.save_evaluation(evaluation) + return evaluation + + +async def persist_interview_close( + api_client: SessionApiClient, + evaluations: List[Dict[str, Any]], + transcript_log: List[Dict[str, Any]], +) -> None: + """Persist final summary and full conversational transcript.""" + await api_client.save_final_summary(evaluations) + await api_client.save_interview_metadata(transcript_log) diff --git a/services/ai-core-services/src/screening_pipeline/prompt_builder.py b/services/ai-core-services/src/screening_pipeline/prompt_builder.py new file mode 100644 index 0000000..60d8a53 --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/prompt_builder.py @@ -0,0 +1,59 @@ +"""User-prompt builders for the AI screening pipeline. + +System prompt constants remain in prompts.py; this module builds the +per-request user prompts passed to the LLM. +""" + +from __future__ import annotations + +from typing import Dict, List, Optional + + +class ScreeningPromptBuilder: + """Builds LLM user prompts for intent routing and answer evaluation.""" + + def build_intent_prompt(self, current_question: str, transcript: str) -> str: + return ( + f"Current Interview Question: {current_question}\n" + f"Candidate Speech: {transcript}" + ) + + def build_evaluation_prompt( + self, + current_question: str, + transcript: str, + expected_keywords: str, + answer_depth: str, + follow_up_context: Optional[List[Dict]] = None, + ) -> str: + parts = [ + "You are evaluating a candidate's answer in a FIRST SCREENING interview.\n", + ] + + if follow_up_context: + parts.append( + "NOTE: This is a FOLLOW-UP evaluation. " + "The candidate had an insufficient primary answer.\n" + ) + + parts.append(f"QUESTION: {current_question}\n") + + candidate_answer = "" + if follow_up_context: + for fu in follow_up_context: + candidate_answer += ( + f"AI: {fu.get('ai_response', '')}\n" + f"Candidate: {fu.get('candidate_speech', '')}\n" + ) + candidate_answer += f"Candidate Latest Answer: {transcript}" + + parts.append(f"CANDIDATE ANSWER: {candidate_answer}\n") + parts.append( + f"EXPECTED KEYWORDS (answer should address most of these): {expected_keywords}\n" + ) + parts.append(f"EVALUATION STRICTNESS LEVEL: {answer_depth}\n") + + return "\n".join(parts) + + +screening_prompt_builder = ScreeningPromptBuilder() diff --git a/services/ai-core-services/src/screening_pipeline/session_api.py b/services/ai-core-services/src/screening_pipeline/session_api.py index 53d1187..5adffcd 100644 --- a/services/ai-core-services/src/screening_pipeline/session_api.py +++ b/services/ai-core-services/src/screening_pipeline/session_api.py @@ -7,7 +7,7 @@ from typing import Dict, Any, List from src.core.config import settings from src.core.logger import logger -from src.screening_pipeline.prompts import RECOMMENDATION_THRESHOLD +from src.screening_pipeline.summary_calculator import compute_final_summary class SessionApiClient: @@ -55,40 +55,14 @@ async def save_evaluation(self, evaluation: Dict[str, Any]): async def save_final_summary(self, evaluations: List[Dict[str, Any]]): """Calculates the final_summary from all evaluations and sends it to core-api.""" - total_questions = len(evaluations) - if total_questions == 0: + final_summary = compute_final_summary(evaluations) + if final_summary is None: return - # Topic Score Resolution (AI_PROCESSING.md Section 5.4) - total_score = 0.0 - for ev in evaluations: - primary_score = ev.get("score", 0) - follow_ups = ev.get("follow_ups", []) - if follow_ups and follow_ups[0].get("score") is not None: - topic_score = (primary_score + follow_ups[0]["score"]) / 2 - else: - topic_score = primary_score - total_score += topic_score - - max_possible_score = total_questions * 10 - overall_score = round((total_score / max_possible_score) * 10, 1) - - if overall_score >= RECOMMENDATION_THRESHOLD: - final_recommendation = "shortlist_for_l1" - else: - final_recommendation = "reject" - - final_summary = { - "total_score": total_score, - "max_possible_score": max_possible_score, - "overall_score": overall_score, - "final_recommendation": final_recommendation - } - logger.info("Saving final summary to core-api", extra={ "session_id": self.session_id, - "overall_score": overall_score, - "recommendation": final_recommendation + "overall_score": final_summary["overall_score"], + "recommendation": final_summary["final_recommendation"] }) try: diff --git a/services/ai-core-services/src/screening_pipeline/speech_filter.py b/services/ai-core-services/src/screening_pipeline/speech_filter.py new file mode 100644 index 0000000..6f27096 --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/speech_filter.py @@ -0,0 +1,28 @@ +"""Pure helpers for filtering STT noise / Whisper hallucinations.""" + +from __future__ import annotations + +import re + +# Common Whisper hallucinations from silence / background noise +WHISPER_HALLUCINATIONS = frozenset({ + "thank you", + "thanks", + "you", + "okay", + "ok", + "yeah", + "thank you for watching", + "thanks for watching", + "subscribe", + "no i dont know", + "okay i dont know", + "i dont know", +}) + + +def is_probable_hallucination(transcript: str, *, min_length: int = 2) -> bool: + """Return True if transcript looks like noise or a known Whisper hallucination.""" + cleaned = transcript.strip().lower() + cleaned = re.sub(r"[^\w\s]", "", cleaned) + return cleaned in WHISPER_HALLUCINATIONS or len(cleaned) < min_length diff --git a/services/ai-core-services/src/screening_pipeline/summary_calculator.py b/services/ai-core-services/src/screening_pipeline/summary_calculator.py new file mode 100644 index 0000000..d279b0c --- /dev/null +++ b/services/ai-core-services/src/screening_pipeline/summary_calculator.py @@ -0,0 +1,43 @@ +"""Pure scoring helpers for interview final summary (no I/O).""" + +from __future__ import annotations + +from typing import Any, Dict, List + +from src.screening_pipeline.prompts import RECOMMENDATION_THRESHOLD + + +def compute_final_summary( + evaluations: List[Dict[str, Any]], + *, + recommendation_threshold: float = RECOMMENDATION_THRESHOLD, +) -> Dict[str, Any] | None: + """Calculate final_summary from per-question evaluations. Returns None if empty.""" + total_questions = len(evaluations) + if total_questions == 0: + return None + + total_score = 0.0 + for ev in evaluations: + primary_score = ev.get("score", 0) + follow_ups = ev.get("follow_ups", []) + if follow_ups and follow_ups[0].get("score") is not None: + topic_score = (primary_score + follow_ups[0]["score"]) / 2 + else: + topic_score = primary_score + total_score += topic_score + + max_possible_score = total_questions * 10 + overall_score = round((total_score / max_possible_score) * 10, 1) + + if overall_score >= recommendation_threshold: + final_recommendation = "shortlist_for_l1" + else: + final_recommendation = "reject" + + return { + "total_score": total_score, + "max_possible_score": max_possible_score, + "overall_score": overall_score, + "final_recommendation": final_recommendation, + } diff --git a/services/ai-core-services/tests/api/v1/test_meeting_bot.py b/services/ai-core-services/tests/api/v1/test_meeting_bot.py new file mode 100644 index 0000000..9f4858d --- /dev/null +++ b/services/ai-core-services/tests/api/v1/test_meeting_bot.py @@ -0,0 +1,113 @@ +from unittest.mock import AsyncMock, patch + +import pytest + +from src.meeting_bot.schemas import ( + BotStatusResponse, + DispatchBotResponse, + LeaveBotResponse, +) + + +@pytest.mark.asyncio +async def test_dispatch_bot_success(client): + mock = DispatchBotResponse( + bot_id="bot-1", + interview_session_id="sess-1", + status="scheduled", + meeting_url="https://meet.google.com/abc", + dispatched_at="2026-01-01T00:00:00+00:00", + ) + with patch( + "src.api.v1.meeting_bot.bot_client.dispatch_bot", + new_callable=AsyncMock, + return_value=mock, + ): + response = await client.post( + "/screening/bot/dispatch", + json={"interview_session_id": "sess-1"}, + ) + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "scheduled" + assert body["bot_id"] == "bot-1" + + +@pytest.mark.asyncio +async def test_dispatch_bot_returns_structured_error(client): + with patch( + "src.api.v1.meeting_bot.bot_client.dispatch_bot", + new_callable=AsyncMock, + side_effect=ValueError("Cannot dispatch bot. The scheduled interview time is in the past."), + ): + response = await client.post( + "/screening/bot/dispatch", + json={"interview_session_id": "sess-2"}, + ) + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "error" + assert "past" in body["error_message"] + + +@pytest.mark.asyncio +async def test_get_bot_status_success(client): + mock = BotStatusResponse( + bot_id="bot-1", + status="ready", + meeting_url="https://meet.google.com/abc", + duration_seconds=10, + ) + with patch( + "src.api.v1.meeting_bot.bot_client.get_bot_status", + new_callable=AsyncMock, + return_value=mock, + ): + response = await client.get("/screening/bot/bot-1") + + assert response.status_code == 200 + assert response.json()["status"] == "ready" + + +@pytest.mark.asyncio +async def test_leave_bot_error_body(client): + with patch( + "src.api.v1.meeting_bot.bot_client.leave_bot", + new_callable=AsyncMock, + side_effect=RuntimeError("attendee down"), + ): + response = await client.post("/screening/bot/bot-1/leave") + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "error" + assert "attendee down" in body["error_message"] + + +@pytest.mark.asyncio +async def test_delete_bot_success_204(client): + with patch( + "src.api.v1.meeting_bot.bot_client.delete_bot", + new_callable=AsyncMock, + return_value=True, + ): + response = await client.delete("/screening/bot/bot-1") + + assert response.status_code == 204 + + +@pytest.mark.asyncio +async def test_delete_bot_failure_structured(client): + with patch( + "src.api.v1.meeting_bot.bot_client.delete_bot", + new_callable=AsyncMock, + return_value=False, + ): + response = await client.delete("/screening/bot/bot-1") + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "error" + assert body["bot_id"] == "bot-1" diff --git a/services/ai-core-services/tests/api/v1/test_parsing.py b/services/ai-core-services/tests/api/v1/test_parsing.py new file mode 100644 index 0000000..fafb26d --- /dev/null +++ b/services/ai-core-services/tests/api/v1/test_parsing.py @@ -0,0 +1,59 @@ +from unittest.mock import AsyncMock, patch + +import pytest + +from src.parsing.schemas import ParsedJDData, ParsedResumeData + + +@pytest.mark.asyncio +async def test_parse_resume_endpoint_success(client): + with patch( + "src.api.v1.parsing.resume_parser.parse_s3_file", + new_callable=AsyncMock, + return_value=ParsedResumeData(primary_skills=["Python"]), + ): + response = await client.post( + "/internal/v1/parse/resume", + json={"resume_name": "a.pdf", "resume_path": "resumes/a.pdf"}, + ) + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "success" + assert body["parsed_resume"]["primary_skills"] == ["Python"] + + +@pytest.mark.asyncio +async def test_parse_resume_endpoint_error(client): + with patch( + "src.api.v1.parsing.resume_parser.parse_s3_file", + new_callable=AsyncMock, + side_effect=RuntimeError("s3 missing"), + ): + response = await client.post( + "/internal/v1/parse/resume", + json={"resume_name": "b.pdf", "resume_path": "resumes/b.pdf"}, + ) + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "error" + assert "s3 missing" in body["error_message"] + + +@pytest.mark.asyncio +async def test_parse_jd_endpoint_success(client): + with patch( + "src.api.v1.parsing.jd_parser.parse_jd_text", + new_callable=AsyncMock, + return_value=ParsedJDData(title="Engineer"), + ): + response = await client.post( + "/internal/v1/parse/jd", + json={"title": "Engineer", "description": "Build APIs"}, + ) + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "success" + assert body["parsed_jd"]["title"] == "Engineer" diff --git a/services/ai-core-services/tests/api/v1/test_question_generation.py b/services/ai-core-services/tests/api/v1/test_question_generation.py new file mode 100644 index 0000000..621ebd4 --- /dev/null +++ b/services/ai-core-services/tests/api/v1/test_question_generation.py @@ -0,0 +1,70 @@ +from unittest.mock import AsyncMock, patch + +import pytest + +from src.parsing.schemas import ParsedJDData +from src.question_generation.schemas import GenerateQuestionsResponse, GeneratedQuestion + + +@pytest.mark.asyncio +async def test_generate_questions_endpoint_success(client): + mock_response = GenerateQuestionsResponse( + interview_session_id="sess-1", + status="success", + questions=[ + GeneratedQuestion( + id=1, + category="must_have_matched", + skill_focus="Python", + question="Explain list vs tuple?", + expected_keywords=["mutable"], + answer_depth="aware", + ) + ], + count=1, + ) + + with patch( + "src.api.v1.question_generation.question_generator.generate", + new_callable=AsyncMock, + return_value=mock_response, + ): + response = await client.post( + "/internal/v1/screening/questions/generate", + json={ + "interview_session_id": "sess-1", + "parsed_jd": ParsedJDData(title="Engineer").model_dump(), + }, + ) + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "success" + assert body["count"] == 1 + + +@pytest.mark.asyncio +async def test_generate_questions_endpoint_error_body(client): + mock_response = GenerateQuestionsResponse( + interview_session_id="sess-2", + status="error", + error_message="LLM failed", + ) + + with patch( + "src.api.v1.question_generation.question_generator.generate", + new_callable=AsyncMock, + return_value=mock_response, + ): + response = await client.post( + "/internal/v1/screening/questions/generate", + json={ + "interview_session_id": "sess-2", + "parsed_jd": ParsedJDData(title="Engineer").model_dump(), + }, + ) + + assert response.status_code == 200 + body = response.json() + assert body["status"] == "error" + assert body["error_message"] == "LLM failed" diff --git a/services/ai-core-services/tests/conftest.py b/services/ai-core-services/tests/conftest.py index 5126a9f..9fd06f7 100644 --- a/services/ai-core-services/tests/conftest.py +++ b/services/ai-core-services/tests/conftest.py @@ -1 +1,32 @@ -"""Shared pytest configuration for ai-core-services.""" +"""Shared pytest configuration for ai-core-services. + +Sets missing Settings fields so collection can import modules that load config. +""" + +from __future__ import annotations + +import os + +# Defaults for local/unit tests when .env is incomplete (do not override real values). +_TEST_ENV_DEFAULTS = { + "WEBHOOK_URL": "http://localhost:8002/screening/webhook", + "CORE_API_URL": "http://localhost:8000", + "WEBSOCKET_URL": "ws://localhost:8002/attendee-websocket", + "SERVICE_NAME": "ai-core-services", + "PORT": "8002", + "ENVIRONMENT": "test", + "LOG_LEVEL": "INFO", + "OLLAMA_URL": "http://localhost:11434", + "OLLAMA_MODEL": "test-model", + "OLLAMA_API_KEY": "test-key", + "ATTENDEE_API_KEY": "", + "ATTENDEE_API_URL": "https://api.attendee.dev/v1", + "DATABASE_URL": "postgresql://user:pass@localhost:5432/ezscreen", + "MINIO_ENDPOINT": "localhost:9000", + "MINIO_ACCESS_KEY": "minio", + "MINIO_SECRET_KEY": "minio", + "MINIO_BUCKET_NAME": "resumes", +} + +for _key, _value in _TEST_ENV_DEFAULTS.items(): + os.environ.setdefault(_key, _value) diff --git a/services/ai-core-services/tests/meeting_bot/__init__.py b/services/ai-core-services/tests/meeting_bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/tests/meeting_bot/test_client.py b/services/ai-core-services/tests/meeting_bot/test_client.py new file mode 100644 index 0000000..8756bff --- /dev/null +++ b/services/ai-core-services/tests/meeting_bot/test_client.py @@ -0,0 +1,28 @@ +from datetime import datetime, timezone +from types import SimpleNamespace +from unittest.mock import AsyncMock, patch + +import pytest + +from src.meeting_bot.client import AttendeeBotClient +from src.meeting_bot.schemas import DispatchBotRequest + + +@pytest.mark.asyncio +async def test_dispatch_bot_rejects_past_schedule(): + # Calendar stores IST labeled as UTC; client subtracts 5h30m → past relative to now. + session = SimpleNamespace( + scheduled_at=datetime.now(timezone.utc).isoformat(), + comment="https://meet.google.com/abc", + ) + client = AttendeeBotClient() + + with patch( + "src.meeting_bot.client.interview_session_repo.get_by_id", + new_callable=AsyncMock, + return_value=session, + ): + with pytest.raises(ValueError, match="in the past"): + await client.dispatch_bot( + DispatchBotRequest(interview_session_id="sess-1") + ) diff --git a/services/ai-core-services/tests/question_generation/__init__.py b/services/ai-core-services/tests/question_generation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/tests/question_generation/test_generator.py b/services/ai-core-services/tests/question_generation/test_generator.py new file mode 100644 index 0000000..af53860 --- /dev/null +++ b/services/ai-core-services/tests/question_generation/test_generator.py @@ -0,0 +1,54 @@ +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from src.parsing.schemas import ParsedJDData +from src.question_generation.generator import QuestionGenerator +from src.question_generation.schemas import GenerateQuestionsRequest, GeneratedQuestion + + +@pytest.mark.asyncio +async def test_question_generator_success_with_mocked_llm(): + llm = MagicMock() + llm.openai_chat_generate = AsyncMock( + return_value=MagicMock( + response=[ + { + "id": 1, + "category": "must_have_matched", + "skill_focus": "Python", + "question": "Describe async/await.", + "expected_keywords": ["event loop"], + "answer_depth": "partial_depth", + } + ] + ) + ) + generator = QuestionGenerator(llm_client=llm) + request = GenerateQuestionsRequest( + interview_session_id="session-1", + parsed_jd=ParsedJDData(title="Engineer"), + ) + + result = await generator.generate(request) + + assert result.status == "success" + assert result.count == 1 + assert isinstance(result.questions[0], GeneratedQuestion) + llm.openai_chat_generate.assert_awaited_once() + + +@pytest.mark.asyncio +async def test_question_generator_error_on_llm_failure(): + llm = MagicMock() + llm.openai_chat_generate = AsyncMock(side_effect=RuntimeError("ollama down")) + generator = QuestionGenerator(llm_client=llm) + request = GenerateQuestionsRequest( + interview_session_id="session-2", + parsed_jd=ParsedJDData(title="Engineer"), + ) + + result = await generator.generate(request) + + assert result.status == "error" + assert "ollama down" in result.error_message diff --git a/services/ai-core-services/tests/question_generation/test_match_context.py b/services/ai-core-services/tests/question_generation/test_match_context.py new file mode 100644 index 0000000..4d4d2d4 --- /dev/null +++ b/services/ai-core-services/tests/question_generation/test_match_context.py @@ -0,0 +1,19 @@ +from src.parsing.schemas import JDSkills, ParsedJDData, SkillRequirement +from src.question_generation.match_context import neutral_job_fit_analysis + + +def test_neutral_job_fit_analysis_uses_jd_skills(): + jd = ParsedJDData( + title="Backend Engineer", + skills=JDSkills( + must_have=[SkillRequirement(skill="Python"), SkillRequirement(skill="SQL")], + good_to_have=[SkillRequirement(skill="Redis")], + ), + ) + + result = neutral_job_fit_analysis(jd) + + assert result["match_score"] == 0.0 + assert result["matched_skills"]["must_have"] == ["Python", "SQL"] + assert result["missing_skills"]["good_to_have"] == ["Redis"] + assert "Job-level screening bank" in result["reasoning"][0] diff --git a/services/ai-core-services/tests/question_generation/test_question_parsing.py b/services/ai-core-services/tests/question_generation/test_question_parsing.py new file mode 100644 index 0000000..c0864b8 --- /dev/null +++ b/services/ai-core-services/tests/question_generation/test_question_parsing.py @@ -0,0 +1,45 @@ +import pytest + +from src.question_generation.question_parsing import parse_questions + + +def test_parse_questions_from_list(): + questions = parse_questions( + [ + { + "id": 1, + "category": "must_have_matched", + "skill_focus": "Python", + "question": "Explain GIL?", + "expected_keywords": ["threads", "lock"], + "answer_depth": "partial_depth", + } + ] + ) + + assert len(questions) == 1 + assert questions[0].skill_focus == "Python" + + +def test_parse_questions_from_wrapped_dict(): + questions = parse_questions( + { + "questions": [ + { + "id": 2, + "category": "lacking_skill", + "skill_focus": "Docker", + "question": "What is a container?", + "expected_keywords": ["isolation"], + "answer_depth": "aware", + } + ] + } + ) + + assert questions[0].id == 2 + + +def test_parse_questions_rejects_non_list(): + with pytest.raises(ValueError, match="Expected JSON array"): + parse_questions({"nope": True}) diff --git a/services/ai-core-services/tests/screening_pipeline/__init__.py b/services/ai-core-services/tests/screening_pipeline/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ai-core-services/tests/screening_pipeline/test_evaluator_helpers.py b/services/ai-core-services/tests/screening_pipeline/test_evaluator_helpers.py new file mode 100644 index 0000000..c797341 --- /dev/null +++ b/services/ai-core-services/tests/screening_pipeline/test_evaluator_helpers.py @@ -0,0 +1,93 @@ +from src.screening_pipeline.evaluation_builders import ( + build_evaluation_block, + build_qa_entry, + build_skip_evaluation, +) +from src.screening_pipeline.prompt_builder import screening_prompt_builder +from src.screening_pipeline.speech_filter import is_probable_hallucination + + +def test_build_skip_evaluation(): + question = { + "id": 1, + "question": "What is Docker?", + "expected_keywords": ["container", "image"], + } + result = build_skip_evaluation(question, "I don't know") + + assert result["score"] == 0 + assert result["question_id"] == 1 + assert result["keywords_missing"] == ["container", "image"] + assert result["decision"] == "NEXT_QUESTION" + + +def test_build_qa_entry_with_follow_ups(): + question = {"id": 2} + follow_ups = [ + {"ai_response": "Can you elaborate?", "candidate_speech": "Containers isolate apps"} + ] + result = build_qa_entry( + question, "What is Docker?", "It runs containers", follow_ups + ) + + assert result["bot_speech"] == "What is Docker?" + assert result["candidate_answer"] == "It runs containers" + assert len(result["follow_ups"]) == 1 + assert result["follow_ups"][0]["bot_speech"] == "Can you elaborate?" + + +def test_build_evaluation_block_uses_primary_eval_when_present(): + question = {"id": 3} + primary = { + "score": 4, + "coverage_percent": 30, + "keywords_found": ["container"], + "keywords_missing": ["image"], + "decision": "ASK_FOLLOW_UP", + "feedback": "Too shallow", + } + current = { + "score": 8, + "coverage_percent": 80, + "keywords_found": ["container", "image"], + "keywords_missing": [], + "decision": "NEXT_QUESTION", + "feedback": "Better", + } + follow_ups = [ + {"ai_response": "Tell me more", "candidate_speech": "Images and containers"} + ] + + result = build_evaluation_block( + question, "What is Docker?", "Images and containers", primary, current, follow_ups + ) + + assert result["score"] == 4 + assert result["follow_ups"][0]["score"] == 8 + + +def test_build_intent_prompt(): + prompt = screening_prompt_builder.build_intent_prompt("What is Python?", "It is a language") + assert "What is Python?" in prompt + assert "It is a language" in prompt + + +def test_build_evaluation_prompt_marks_follow_up(): + prompt = screening_prompt_builder.build_evaluation_prompt( + current_question="What is Docker?", + transcript="Containers", + expected_keywords="container, image", + answer_depth="partial_depth", + follow_up_context=[ + {"ai_response": "Say more", "candidate_speech": "boxes"} + ], + ) + assert "FOLLOW-UP evaluation" in prompt + assert "partial_depth" in prompt + + +def test_is_probable_hallucination_filters_noise(): + assert is_probable_hallucination("thank you") is True + assert is_probable_hallucination("Thanks for watching!") is True + assert is_probable_hallucination("a") is True + assert is_probable_hallucination("Docker isolates processes in containers") is False diff --git a/services/ai-core-services/tests/screening_pipeline/test_orchestrator.py b/services/ai-core-services/tests/screening_pipeline/test_orchestrator.py new file mode 100644 index 0000000..fa347a8 --- /dev/null +++ b/services/ai-core-services/tests/screening_pipeline/test_orchestrator.py @@ -0,0 +1,28 @@ +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from src.screening_pipeline.orchestrator import InterviewOrchestrator + + +@pytest.mark.asyncio +async def test_orchestrator_start_aborts_without_session(): + ws = MagicMock() + orch = InterviewOrchestrator( + "missing-session", + ws, + stt_client=MagicMock(), + tts_client=MagicMock(), + evaluator=MagicMock(), + llm_client=MagicMock(), + api_client=MagicMock(), + ) + + with patch( + "src.screening_pipeline.orchestrator.interview_session_repo.get_by_id", + new_callable=AsyncMock, + return_value=None, + ): + await orch.start() + + assert orch.is_active is False diff --git a/services/ai-core-services/tests/screening_pipeline/test_persistence.py b/services/ai-core-services/tests/screening_pipeline/test_persistence.py new file mode 100644 index 0000000..0ce1825 --- /dev/null +++ b/services/ai-core-services/tests/screening_pipeline/test_persistence.py @@ -0,0 +1,51 @@ +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from src.screening_pipeline.persistence import ( + persist_completed_question, + persist_interview_close, +) + + +@pytest.mark.asyncio +async def test_persist_completed_question_saves_transcript_and_eval(): + api_client = MagicMock() + api_client.save_transcript = AsyncMock() + api_client.save_evaluation = AsyncMock() + evaluations: list = [] + + with patch( + "src.screening_pipeline.persistence.AnswerEvaluator.build_qa_entry", + return_value={"question_id": 1}, + ), patch( + "src.screening_pipeline.persistence.AnswerEvaluator.build_evaluation_block", + return_value={"question_id": 1, "score": 8}, + ): + result = await persist_completed_question( + api_client, + evaluations, + question_obj={"id": 1}, + current_q="What is Docker?", + transcript="Containers", + primary_eval={"score": 8}, + current_eval={"score": 8}, + follow_ups=None, + ) + + assert result["score"] == 8 + assert len(evaluations) == 1 + api_client.save_transcript.assert_awaited_once() + api_client.save_evaluation.assert_awaited_once() + + +@pytest.mark.asyncio +async def test_persist_interview_close_calls_summary_and_metadata(): + api_client = MagicMock() + api_client.save_final_summary = AsyncMock() + api_client.save_interview_metadata = AsyncMock() + + await persist_interview_close(api_client, [{"score": 7}], [{"bot_speech": "hi"}]) + + api_client.save_final_summary.assert_awaited_once() + api_client.save_interview_metadata.assert_awaited_once() diff --git a/services/ai-core-services/tests/screening_pipeline/test_summary_calculator.py b/services/ai-core-services/tests/screening_pipeline/test_summary_calculator.py new file mode 100644 index 0000000..86eebb4 --- /dev/null +++ b/services/ai-core-services/tests/screening_pipeline/test_summary_calculator.py @@ -0,0 +1,27 @@ +from src.screening_pipeline.summary_calculator import compute_final_summary + + +def test_compute_final_summary_empty(): + assert compute_final_summary([]) is None + + +def test_compute_final_summary_shortlists_high_scores(): + evaluations = [ + {"score": 8, "follow_ups": []}, + {"score": 9, "follow_ups": [{"score": 7}]}, + ] + result = compute_final_summary(evaluations) + + assert result["max_possible_score"] == 20 + assert result["overall_score"] >= 6.0 + assert result["final_recommendation"] == "shortlist_for_l1" + + +def test_compute_final_summary_rejects_low_scores(): + evaluations = [ + {"score": 2, "follow_ups": []}, + {"score": 3, "follow_ups": []}, + ] + result = compute_final_summary(evaluations) + + assert result["final_recommendation"] == "reject" diff --git a/services/ai-core-services/tests/screening_pipeline/test_webhook_handler.py b/services/ai-core-services/tests/screening_pipeline/test_webhook_handler.py new file mode 100644 index 0000000..a2f12ab --- /dev/null +++ b/services/ai-core-services/tests/screening_pipeline/test_webhook_handler.py @@ -0,0 +1,40 @@ +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from src.screening_pipeline.webhook_handler import process_state_change + + +@pytest.mark.asyncio +async def test_process_state_change_marks_in_progress_on_joined(): + session = MagicMock() + session.id = "sess-1" + + with patch( + "src.screening_pipeline.webhook_handler.interview_session_repo.get_by_bot_id", + new_callable=AsyncMock, + return_value=session, + ), patch( + "src.screening_pipeline.webhook_handler.update_session_status", + new_callable=AsyncMock, + ) as mock_update: + await process_state_change( + {"data": {"bot_id": "bot-1", "state": "joined_recording"}} + ) + + mock_update.assert_awaited_once_with("sess-1", "in_progress") + + +@pytest.mark.asyncio +async def test_process_state_change_ignores_unknown_bot(): + with patch( + "src.screening_pipeline.webhook_handler.interview_session_repo.get_by_bot_id", + new_callable=AsyncMock, + return_value=None, + ), patch( + "src.screening_pipeline.webhook_handler.update_session_status", + new_callable=AsyncMock, + ) as mock_update: + await process_state_change({"data": {"bot_id": "missing", "state": "ended"}}) + + mock_update.assert_not_awaited() diff --git a/services/ai-core-services/uv.lock b/services/ai-core-services/uv.lock index 6144a0d..c9ca13e 100644 --- a/services/ai-core-services/uv.lock +++ b/services/ai-core-services/uv.lock @@ -1,11 +1,15 @@ version = 1 requires-python = ">=3.11" resolution-markers = [ - "sys_platform == 'darwin'", - "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.15' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten')", - "platform_system != 'Linux' and sys_platform == 'win32'", + "python_full_version < '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.14' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten')", + "python_full_version == '3.14.*' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten'", + "python_full_version < '3.14' and platform_system != 'Linux' and sys_platform == 'win32'", + "python_full_version >= '3.14' and platform_system != 'Linux' and sys_platform == 'win32'", "python_full_version < '3.12' and platform_system != 'Linux' and sys_platform == 'emscripten'", - "platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.14' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.14' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and platform_system == 'Linux' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and platform_system == 'Linux' and sys_platform == 'emscripten'", "python_full_version >= '3.12' and python_full_version < '3.14' and platform_system != 'Linux' and sys_platform == 'emscripten'", @@ -40,14 +44,18 @@ dependencies = [ { name = "docling", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "fastapi", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "httpx", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "kokoro-onnx", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "openai", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "psycopg2-binary", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "pydantic", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "pydantic-settings", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "python-json-logger", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "pyyaml", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "soundfile", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "sqlalchemy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "uvicorn", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "webrtcvad", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "websockets", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, ] @@ -65,14 +73,18 @@ requires-dist = [ { name = "docling", specifier = ">=2.15.0" }, { name = "fastapi", specifier = ">=0.100.0" }, { name = "httpx", specifier = ">=0.24.1" }, + { name = "kokoro-onnx", specifier = ">=0.1.6" }, + { name = "numpy", specifier = ">=1.26.0" }, { name = "openai", specifier = ">=1.0.0" }, { name = "psycopg2-binary", specifier = ">=2.9.6" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "pydantic-settings", specifier = ">=2.0.0" }, { name = "python-json-logger", specifier = ">=2.0.7" }, { name = "pyyaml", specifier = ">=6.0.0" }, + { name = "soundfile", specifier = ">=0.12.1" }, { name = "sqlalchemy", specifier = ">=2.0.0" }, { name = "uvicorn", specifier = ">=0.22.0" }, + { name = "webrtcvad", specifier = ">=2.0.10" }, { name = "websockets", specifier = ">=11.0.3" }, ] @@ -180,6 +192,104 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983 }, ] +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "(python_full_version < '3.15' and implementation_name != 'PyPy') or (implementation_name != 'PyPy' and platform_system != 'Linux') or (implementation_name != 'PyPy' and sys_platform == 'darwin') or (implementation_name != 'PyPy' and sys_platform == 'emscripten')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/d2/16d99a0c4948febc0ebd133a13b2f688ff7f8cb04da971e1128872ce0c03/cffi-2.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12", size = 183838 }, + { url = "https://files.pythonhosted.org/packages/cd/95/31b535a9f0220ae9f357de4a08d57ce89cb417653c2fd9f075f50822a388/cffi-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1", size = 184168 }, + { url = "https://files.pythonhosted.org/packages/ad/5a/4707a0dc1f203f5dde5a907b0d4e3c25d71120241048bd5bc6f1bb9d4e71/cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0", size = 211805 }, + { url = "https://files.pythonhosted.org/packages/ad/66/c19feabb28485b6e0bbaaafa90837a1ef5d302e90f2178bd33f17a49879b/cffi-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813", size = 218716 }, + { url = "https://files.pythonhosted.org/packages/a7/92/500760486c8baab49a7a8a58ba7fc3355ec3974b454b8a09e528efde9e1d/cffi-2.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990", size = 205569 }, + { url = "https://files.pythonhosted.org/packages/a5/a7/a67c733254d6e7373f7822f8082d8d6beade791e0cf12a7611f376fa61c7/cffi-2.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af", size = 204907 }, + { url = "https://files.pythonhosted.org/packages/f7/a4/4399daaf8f7dfee9d7c3327fdb0426ee041cc63edc358b93911ceb2bfc7a/cffi-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632", size = 217807 }, + { url = "https://files.pythonhosted.org/packages/28/f7/dabe6da2466ecbd82dc62e7342dc6b1065dad990c06f00f0ede9ebf2a0ed/cffi-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd", size = 221252 }, + { url = "https://files.pythonhosted.org/packages/ce/87/616202d8e51342c07d2534c510111c4cc37201775ce8f60802c9335d1edd/cffi-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a", size = 214214 }, + { url = "https://files.pythonhosted.org/packages/b4/c6/ab025d75d2c26c19b087c0124e75ee31cb65032f4fe345d356d8c507ab97/cffi-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa", size = 219408 }, + { url = "https://files.pythonhosted.org/packages/db/e2/7e8109f65445bdc673a7b54f02c677de462db75674220fd1335efc8eb598/cffi-2.1.1-cp311-cp311-win32.whl", hash = "sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3", size = 174470 }, + { url = "https://files.pythonhosted.org/packages/73/c0/77ba02423c2f7d7091143c45cd49e0e6575c4c1967394bb542bd923a9b74/cffi-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0", size = 185096 }, + { url = "https://files.pythonhosted.org/packages/7c/47/9f1f85f9672ceda4984dc6c4f8824e8558992a2972c3d3c81fb8eb28d4ba/cffi-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455", size = 179941 }, + { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821 }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719 }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799 }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389 }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249 }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775 }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822 }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232 }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597 }, + { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292 }, + { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919 }, + { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093 }, + { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248 }, + { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908 }, + { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805 }, + { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764 }, + { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722 }, + { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369 }, + { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175 }, + { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670 }, + { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824 }, + { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148 }, + { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564 }, + { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263 }, + { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688 }, + { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078 }, + { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064 }, + { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720 }, + { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964 }, + { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962 }, + { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328 }, + { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985 }, + { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530 }, + { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525 }, + { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053 }, + { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213 }, + { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682 }, + { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949 }, + { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947 }, + { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504 }, + { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259 }, + { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864 }, + { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538 }, + { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688 }, + { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803 }, + { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763 }, + { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688 }, + { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868 }, + { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104 }, + { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402 }, + { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043 }, + { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737 }, + { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933 }, + { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002 }, + { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271 }, + { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919 }, + { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529 }, + { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630 }, + { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134 }, + { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197 }, + { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683 }, + { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897 }, + { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935 }, + { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464 }, + { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262 }, + { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779 }, + { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520 }, + { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673 }, + { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835 }, + { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705 }, + { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539 }, + { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707 }, + { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772 }, + { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360 }, +] + [[package]] name = "charset-normalizer" version = "3.5.1" @@ -336,6 +446,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251 }, ] +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -571,6 +690,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019 }, ] +[[package]] +name = "dlinfo" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/8e/8f2f94cd40af1b51e8e371a83b385d622170d42f98776441a6118f4dd682/dlinfo-2.0.0.tar.gz", hash = "sha256:88a2bc04f51d01bc604cdc9eb1c3cc0bde89057532ca6a3e71a41f6235433e17", size = 12727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/90/022c79d6e5e6f843268c10b84d4a021ee3afba0621d3c176d3ff2024bfc8/dlinfo-2.0.0-py3-none-any.whl", hash = "sha256:b32cc18e3ea67c0ca9ca409e5b41eed863bd1363dbc9dd3de90fedf11b61e7bc", size = 3654 }, +] + [[package]] name = "doclang" version = "0.7.3" @@ -740,6 +868,19 @@ standard = [ { name = "websockets", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, ] +[[package]] +name = "espeakng-loader" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/92/f44ed7f531143c3c6c97d56e2b0f9be8728dc05e18b96d46eb539230ed46/espeakng_loader-0.2.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b77477ae2ddf62a748e04e49714eabb2f3a24f344166200b00539083bd669904", size = 9938387 }, + { url = "https://files.pythonhosted.org/packages/a8/26/258c0cd43b9bc1043301c5f61767d6a6c3b679df82790c9cb43a3277b865/espeakng_loader-0.2.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d27cdca31112226e7299d8562e889d3e38a1e48055c9ee381b45d669072ee59f", size = 9892565 }, + { url = "https://files.pythonhosted.org/packages/de/1e/25ec5ab07528c0fbb215a61800a38eca05c8a99445515a02d7fa5debcb32/espeakng_loader-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08721baf27d13d461f6be6eed9a65277e70d68234ff484fd8b9897b222cdcb6d", size = 10078484 }, + { url = "https://files.pythonhosted.org/packages/d9/ad/1b768d8daffc2996e07bbcb6f534d8de3202cd75fce1f1c45eced1ce6465/espeakng_loader-0.2.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d1e798141b46a050cdb75fcf3c17db969bb2c40394f3f4a48910655d547508b9", size = 10037736 }, + { url = "https://files.pythonhosted.org/packages/9d/ed/a3d872fbad4f3a3f3db0e8c31768ab14e77cd77306de16b8b20b1e1df7ea/espeakng_loader-0.2.4-py3-none-win_amd64.whl", hash = "sha256:41f1e08ac9deda2efd1ea9de0b81dab9f5ae3c4b24284f76533d0a7b1dd7abd7", size = 9437292 }, + { url = "https://files.pythonhosted.org/packages/29/64/0b75bc50ec53b4e000bac913625511215aa96124adf5dba8c4baa17c02cd/espeakng_loader-0.2.4-py3-none-win_arm64.whl", hash = "sha256:d7a2928843eaeb2df82f99a370f44e8a630f59b02f9b0d1f168a03c4eeb76b89", size = 9426841 }, +] + [[package]] name = "et-xmlfile" version = "2.0.0" @@ -795,6 +936,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 }, ] +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661 }, +] + [[package]] name = "fsspec" version = "2026.7.0" @@ -932,8 +1081,8 @@ name = "httpcore2" version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "h11", marker = "(python_full_version < '3.15' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten') or (platform_system != 'Linux' and sys_platform != 'emscripten') or sys_platform == 'darwin'" }, - { name = "truststore", marker = "(python_full_version < '3.15' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten') or (platform_system != 'Linux' and sys_platform != 'emscripten') or sys_platform == 'darwin'" }, + { name = "h11", marker = "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.15' and sys_platform != 'emscripten') or (platform_system != 'Linux' and sys_platform != 'emscripten') or sys_platform == 'darwin'" }, + { name = "truststore", marker = "(python_full_version < '3.12' and platform_system == 'Linux' and sys_platform == 'emscripten') or (python_full_version < '3.15' and sys_platform != 'emscripten') or (platform_system != 'Linux' and sys_platform != 'emscripten') or sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548 } wheels = [ @@ -1126,6 +1275,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419 }, ] +[[package]] +name = "joblib" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/1d/537ab090f302b838943a1b56497dd53059b9a9b46a074936470173a2e207/joblib-1.6.0.tar.gz", hash = "sha256:2ccc96785b12046c08fd6d55839c12857831b54a3c1673ffadd2f04bfc4eda03", size = 327903 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/53/84099323c2ec4be98d935f63c033ac4151ee83836ca1050ede3b3aadf155/joblib-1.6.0-py3-none-any.whl", hash = "sha256:3dbbf9f6e4b592a2357b854608e980fe6390d131d7a82f011a377ef2ebef7aba", size = 306115 }, +] + [[package]] name = "jsonlines" version = "4.0.0" @@ -1174,6 +1335,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437 }, ] +[[package]] +name = "kokoro-onnx" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "espeakng-loader", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "onnxruntime", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "phonemizer", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/ef/b58dedba0a1417f16352352787fbcfcbaa0b907e284c2ea3ebaf5541109a/kokoro_onnx-0.6.1.tar.gz", hash = "sha256:7bbdb66dd53775f71088a99999a9aefdad240740daf54cb09410d8bb1e294e35", size = 69799 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e1/a27e5a70a525a5ee1fd5357596f07b724d02ff317f134e86cb6e3d9db968/kokoro_onnx-0.6.1-py3-none-any.whl", hash = "sha256:50c8de4950d601df41428ee5462a48c8a78bef441bf671f2492e070ef44d8a32", size = 26084 }, +] + [[package]] name = "latex2mathml" version = "3.81.0" @@ -1738,6 +1914,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/0e/152509871bf30df6fc38569f52a2db9b55dd41aae957adae50a053ac7778/omegaconf-2.3.1-py3-none-any.whl", hash = "sha256:3d701d14e9a8828f1edd28bb70b725908b34277cdd72cf7d6a83f94dadc6b6a0", size = 79502 }, ] +[[package]] +name = "onnxruntime" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flatbuffers", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "packaging", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "protobuf", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/a8/0520890321b8ff40b908cf165a93eb58fbc8f85c14db637277ea866c9544/onnxruntime-1.29.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:07c5907474dec4a2792fd7626b753dc66707808385a6d9eecf993db0066a9d0f", size = 21420890 }, + { url = "https://files.pythonhosted.org/packages/0b/77/8bd3e0008ff8d386305351109a7329ea57e51a3ab57bc92340f29c4a5b5d/onnxruntime-1.29.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:16925ef8497e2c07e4b5ae15b504079b3ab3f65e22c58efd10dde0f3caea969a", size = 20803602 }, + { url = "https://files.pythonhosted.org/packages/3b/91/a66cd77f28379ede419672edda3184f1eb286db215dce1e7b976fae2d63b/onnxruntime-1.29.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:85f8e8406c52658735fe5c7fbfd3ebaa1ed340768324f6252e4274e374580a23", size = 23113193 }, + { url = "https://files.pythonhosted.org/packages/1c/82/2da968405c42340f03de0bcdb63be09ae1004f820b2295590d48951b5cf2/onnxruntime-1.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:0d4f427afac434b0070fe992b540ddf20a7aff2265f760f314d91331935b6b98", size = 13999253 }, + { url = "https://files.pythonhosted.org/packages/95/7a/70c9c893bf732ee66124c2d8de6a21fc9361ec62cf378f857043efcbf0eb/onnxruntime-1.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:4eae472cf7dc3107dec1bb53cd6d142d1964616d08aae48654cd4254b2363c4b", size = 13741410 }, + { url = "https://files.pythonhosted.org/packages/d4/80/381c1e9efed9cc32d00aa7cab0547dc84116cec906c3ffe3613686d6963a/onnxruntime-1.29.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3a3814c041251d6a77fdf513fb282056538ee826d2f1178a0df3c549d3fff6ba", size = 21430049 }, + { url = "https://files.pythonhosted.org/packages/30/12/4be0e345d38fe707a701ca07e8f63c05b152a2e6285d1e43a7faf63fedd2/onnxruntime-1.29.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d2fb19e848f7c33ed8d3182b52504aaa11c5e8da438bbb47296f85b133cbcf6b", size = 20816870 }, + { url = "https://files.pythonhosted.org/packages/96/eb/e6968f5e41aac3125f2ff5708855f09cb0b70d85ed3115b625b0b58305ba/onnxruntime-1.29.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2b80d8c7ec2cc7438e4da3760b88c24568cba72c9ace96d668800a6c79419acb", size = 23136745 }, + { url = "https://files.pythonhosted.org/packages/b4/80/5b28f1f1111210fc4a336ddbc6950f468ebf9a6a265420568f4f43fa33ce/onnxruntime-1.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:4acf2b4948b7ede87221ca6332344b8facdc8059d6ac751a7d367d04532b02dd", size = 14001407 }, + { url = "https://files.pythonhosted.org/packages/6f/d6/6883f89ea4b044e6e8447ebfaf9bcecdf457b7d80a683635e130b25498e0/onnxruntime-1.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:dc61a79cb39afd66ab3f01fd2c23591a7f01de89c1668e1fb6315067fc279164", size = 13746981 }, + { url = "https://files.pythonhosted.org/packages/41/f8/d375facf60edaf41f5732f9f689c98a800fcc52df5cf6ddfb406703eb5a1/onnxruntime-1.29.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:be0f8ed688cfb1d4d5765a137193b7bfab0c8ea214eed99260b380bb525a3a7f", size = 21429708 }, + { url = "https://files.pythonhosted.org/packages/c9/17/b9ad04051a8c4f504852ce0e8e10f9a6b2f1a331eedcdcc503df776dd0ea/onnxruntime-1.29.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:d67673c5367727860922c5262d724472f1b5539fb7ccf4c81a638f9b71719803", size = 20816263 }, + { url = "https://files.pythonhosted.org/packages/83/2c/d8eb945d2a372149df9705a8d5c8d7c6c46c987c5446dbcea9e1ea7f6556/onnxruntime-1.29.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e2128f31f449e922c62dbe5d8b6b7b079f0bcaf2d56a102fa203cb6e5bb5ab19", size = 23136817 }, + { url = "https://files.pythonhosted.org/packages/e1/3b/66b424c63fa92dfaa48d1719efaae66fc8c256b9426a832eda51d8dfe1e9/onnxruntime-1.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:2945e1f82f81f27e88decea88c7861f45baea23818950d467bf3909aa303119e", size = 14001310 }, + { url = "https://files.pythonhosted.org/packages/83/22/d6a700e3a6322fa3d56fbe7cee9ffc53f35e77ffcd6b7e97f4b7722a27ab/onnxruntime-1.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:4b940b0d777590c7e20bf298f5c16af1ea6ad1b400a1c822a6be192f64f4d954", size = 13747112 }, + { url = "https://files.pythonhosted.org/packages/4a/89/c4af146de3d60a32c89fea48d5d34bfd044faaf8957270043a03bd1b462b/onnxruntime-1.29.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:533f8370ce124304e5cb08ab961836cf755631e3dd77adc5f3bbdab70c2b7d99", size = 20826136 }, + { url = "https://files.pythonhosted.org/packages/9d/f2/e6bbacd11dfe8d070613261a758795ea128b9fc9bea391a2a7da2e4c7a08/onnxruntime-1.29.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c1ad3f437153fe77f9d01a08fbaac0beb030e09b8a80ace1603bcf69b6c95481", size = 23138951 }, + { url = "https://files.pythonhosted.org/packages/ff/a3/718e1b83096a1bc7b0fc8014c23d4cf795559fe666961cfac4fc038a4871/onnxruntime-1.29.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e74b278af1d949876f5d91d1268fd6c680e79f2bac194967394eaba9fdf69e7e", size = 21431104 }, + { url = "https://files.pythonhosted.org/packages/4e/17/c75e78ddc1fe69b6ebaef7fe88ac83f29bfe10955e3a0d2436d93473c91c/onnxruntime-1.29.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:939e5d65f332e6d399774b2bd0d3559fd8fa629c1e77833db29d968d2384f23d", size = 20818488 }, + { url = "https://files.pythonhosted.org/packages/65/54/9f197c578d3d3d7bea16971e233e5483981228eec73748585cf7b5933403/onnxruntime-1.29.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6c0c37b92f67ed68dd36221ce0403e1d9bd4f7efce724439978a2597848530e5", size = 23136994 }, + { url = "https://files.pythonhosted.org/packages/24/53/4616a55d2495679cfd0195f968feb3d74fe30e26467d168ee243ac97c089/onnxruntime-1.29.0-cp314-cp314-win_amd64.whl", hash = "sha256:4a3129ae56e70d2618ff773920166916310370a7e3cacb60b9e0e8910092725f", size = 14350643 }, + { url = "https://files.pythonhosted.org/packages/0a/0f/c338cb5500a522c7e671a3bb1276f4562404fbecce8a0e274565aa968484/onnxruntime-1.29.0-cp314-cp314-win_arm64.whl", hash = "sha256:e417ef8628dcce310d2d53023e750ea298ec14d4341ae6dc3a572bfd9bc7fa97", size = 14124294 }, + { url = "https://files.pythonhosted.org/packages/b8/e7/61064289a9a1301b25c1f0f574fe98aba31c2d388db3c1dbec664f78621f/onnxruntime-1.29.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:11264bb58f7b7cf6af835ab10d36838d73680580820fd6f51d90124a1ca8f449", size = 20826174 }, + { url = "https://files.pythonhosted.org/packages/60/21/d0c04b561b46e9bff89b5f500fb7415b8ca0669f7902204f76ab06bb0c7e/onnxruntime-1.29.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1ea91cef3b971506e51ae9c37c16d027774ec64994a524ec1bdfb027d68a9832", size = 23138547 }, +] + [[package]] name = "openai" version = "3.5.0" @@ -1800,7 +2013,7 @@ name = "pandas" version = "3.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "python_full_version < '3.14' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "python-dateutil", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, { name = "tzdata", marker = "(platform_system != 'Linux' and sys_platform == 'win32') or sys_platform == 'emscripten'" }, ] @@ -1849,6 +2062,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776 }, ] +[[package]] +name = "phonemizer" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "dlinfo", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "joblib", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/7d/5a96ddb130552f6365a090b5fd12ace803a95a858e3f67258f2ad13dc51f/phonemizer-3.4.0.tar.gz", hash = "sha256:e13231980c50bc671ec0466379ba027260ad9d61929952d8ae9665b3d0f251eb", size = 93535 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/41/09bafd0ccf4f4ddd1530c9f9d10e75a24056e10e120b9daa97f691fcabc3/phonemizer-3.4.0-py3-none-any.whl", hash = "sha256:5ff1215d0efa3606dd1b92449f6d71b5c1741efcc84c6d40c17bfaf64f6ded5f", size = 108529 }, +] + [[package]] name = "pillow" version = "12.3.0" @@ -1956,6 +2184,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dd/34/b6f19941adcdaf415b5e8a8d577499f5b6a76b59cbae37f9b125a9ffe9f2/polyfactory-3.3.0-py3-none-any.whl", hash = "sha256:686abcaa761930d3df87b91e95b26b8d8cb9fdbbbe0b03d5f918acff5c72606e", size = 62707 }, ] +[[package]] +name = "protobuf" +version = "7.36.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/73/f66c748df06e7fe24e658eddd600d19c4b40bad836c97ce2d0ad9851fb6b/protobuf-7.36.1.tar.gz", hash = "sha256:d0f6470f0ce2b84e3feaea2d4b816378b37ba4d4aa08a274305373de93e2d524", size = 512499 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/6c/3a54a58f2948b0f485df9ecdd06590f15d0a7abf46a89d50c3de709ff4ff/protobuf-7.36.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:3cf2ee25d006cee57294a1196ea43b37feb78e0dcd1e8af5c1aeddb777655aca", size = 456046 }, + { url = "https://files.pythonhosted.org/packages/6e/08/9f9548793c771095245c0eeaf0c84b76b16a5ef26158043d456f551344a0/protobuf-7.36.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:43d3d37b1eb24c113b9b7d02008cac44e423f00b611b7781ae998d7623972969", size = 344226 }, + { url = "https://files.pythonhosted.org/packages/fe/51/1bdbd612fa3c51e42ea6f45d05e84dc748ddcd2663e1aa1e89a00a33facd/protobuf-7.36.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:39c518c05586c016d7874ff6079ee115bcec1ea5fbb1d177fbf7867ef4c67e44", size = 357229 }, + { url = "https://files.pythonhosted.org/packages/22/df/c799fe7a05ef16ba853a59db01f3a2c5f7d0676469589ccc4874f76a2a88/protobuf-7.36.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:97198b77e369a0abd8e262b8f6c7266c55ddb796a3a12c76d7b8881188ed83aa", size = 343228 }, + { url = "https://files.pythonhosted.org/packages/3d/33/d4724ec5d86d496fe4108e220618aa0837ad3423a7af7ccdd9684ccc77c8/protobuf-7.36.1-cp310-abi3-win32.whl", hash = "sha256:0b53ce95272aad50ad25d7ff03373743209822e8ba42ea7fad27d2bee1547d00", size = 443002 }, + { url = "https://files.pythonhosted.org/packages/db/37/155788a0d8daded960375af604202805308169f9b859419ea0aa370946e2/protobuf-7.36.1-cp310-abi3-win_amd64.whl", hash = "sha256:51139351435d9b43d88a55eaa49fb6f737fbb478fb0cbf2cf694d1a04a9d3363", size = 456518 }, + { url = "https://files.pythonhosted.org/packages/39/ca/c47f91d3cab175b01fd8c4f0d80fdf8613be876cc616e66ad281a59c5ddf/protobuf-7.36.1-py3-none-any.whl", hash = "sha256:7d951e46b3f963d6c264c367c437921de9d5aedd9c3f9612b9077736b4e3ad5c", size = 179813 }, +] + [[package]] name = "psutil" version = "7.2.2" @@ -2073,6 +2316,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/59/81050abdc9e5b90ffc2c765738c5e40e9abd8e44864aaa737b600f16c562/pyclipper-1.4.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98b2a40f98e1fc1b29e8a6094072e7e0c7dfe901e573bf6cfc6eb7ce84a7ae87", size = 126495 }, ] +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172 }, +] + [[package]] name = "pydantic" version = "2.13.4" @@ -2970,6 +3222,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] +[[package]] +name = "soundfile" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15' or platform_system != 'Linux' or sys_platform == 'darwin' or sys_platform == 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/db/949331952a6fb1c5b12e9de80fd08747966c2039d1a61db4764fbd3981c2/soundfile-0.14.0.tar.gz", hash = "sha256:ba1c1a2d618bca5c406647c83b89f07cc8810fa506a50622a6993ba130c1de11", size = 47842 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/d1/5e338af9ca6ed0786cd5bb03f6d60de1c325728c1189014f3b59aae7403c/soundfile-0.14.0-py2.py3-none-any.whl", hash = "sha256:8ba81ae3a89fd5ab3bef8a8eb481fbbe794e806309675a89b4df48b8d31908a8", size = 26799 }, + { url = "https://files.pythonhosted.org/packages/7e/72/c6b21e58d3113596e7e8de0a08d6f1d95173492cfbca0a4db14148cbba2a/soundfile-0.14.0-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:19be05428da76ed61a4cad29b8e4bcf43a3e5c100089d2ec81dc961eed1b0dd4", size = 1144568 }, + { url = "https://files.pythonhosted.org/packages/63/7a/dfdd6f8c748988427119f75eb860a3cedd858d1aea1fe28f39ad8559ef22/soundfile-0.14.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:d828d35a059626da52f1415b5faee610aeab393319cb3fc4a9aef47b619fc14c", size = 1103726 }, + { url = "https://files.pythonhosted.org/packages/4a/f8/fc39fad6f879633461d27394cd1ddaf1f769ffa0597dca35872f51b16461/soundfile-0.14.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e85724a90bc99a6e8062c0b4ddf725f53b2a3b70afd4da875e9d2cfc4e92f377", size = 1238050 }, + { url = "https://files.pythonhosted.org/packages/7b/a2/70fd4432b924684c372df8b0a45708c36c057ef3596c9eb53e0a806b980b/soundfile-0.14.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:1e38bac1853412871318e82a1ba69a8be677619b56025bbfcccdb41b6cafe82d", size = 1315963 }, + { url = "https://files.pythonhosted.org/packages/d9/34/c9e80783d83eab739a9531fdee03675d53e0bf1b2ccb4bb3af5844675046/soundfile-0.14.0-py2.py3-none-win32.whl", hash = "sha256:0a6ae43c50c71b4e020cc55382925cb89451c1ed1a0c3d0f5d802da269226849", size = 902199 }, + { url = "https://files.pythonhosted.org/packages/ed/97/b39c18ac1df45e755ca22b8b00e872929da5d107998a207a5e4ac831bfda/soundfile-0.14.0-py2.py3-none-win_amd64.whl", hash = "sha256:299491d3499460fb1b74bb4bd78b57ffc2d243a5fafa7b6ec1b264875c78453e", size = 1021480 }, + { url = "https://files.pythonhosted.org/packages/f4/83/55c65e61cf457805ce2ec157c1c6ae17715d0851aa2374422de0538838ca/soundfile-0.14.0-py2.py3-none-win_arm64.whl", hash = "sha256:e090704718e124e7c844695236f1fce8d18a5e761eaf7c82dfcd124620805f98", size = 888858 }, +] + [[package]] name = "soupsieve" version = "2.9.2" @@ -3438,6 +3711,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/79/4a20b54ab0491485ccd8c077db2d39187c7f12b3e15485d38a7be37c81b4/uvicorn-0.52.4-py3-none-any.whl", hash = "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1", size = 79871 }, ] +[[package]] +name = "webrtcvad" +version = "2.0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/34/e2de2d97f3288512b9ea56f92e7452f8207eb5a0096500badf9dfd48f5e6/webrtcvad-2.0.10.tar.gz", hash = "sha256:f1bed2fb25b63fb7b1a55d64090c993c9c9167b28485ae0bcdd81cf6ede96aea", size = 66156 } + [[package]] name = "websockets" version = "16.1.1"