Spaces:
Sleeping
Sleeping
Faham
commited on
Commit
·
31adc25
1
Parent(s):
e501d8f
CREATE: streamlit_app.py for UI
Browse files- .streamlit/config.toml +13 -0
- README.md +58 -0
- main.py → agent_client.py +0 -1
- pyproject.toml +2 -0
- requirements.txt +12 -0
- simple_mcp_test.py +0 -44
- streamlit_app.py +714 -0
- uv.lock +235 -0
.streamlit/config.toml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[server]
|
| 2 |
+
maxUploadSize = 200
|
| 3 |
+
enableXsrfProtection = false
|
| 4 |
+
enableCORS = false
|
| 5 |
+
|
| 6 |
+
[browser]
|
| 7 |
+
gatherUsageStats = false
|
| 8 |
+
|
| 9 |
+
[theme]
|
| 10 |
+
primaryColor = "#1f77b4"
|
| 11 |
+
backgroundColor = "#ffffff"
|
| 12 |
+
secondaryBackgroundColor = "#f0f2f6"
|
| 13 |
+
textColor = "#262730"
|
README.md
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Financial Agent
|
| 2 |
+
|
| 3 |
+
A comprehensive financial analysis tool that provides real-time stock data and news analysis through an AI-powered chat interface.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Real-time Stock Data**: Fetch historical stock prices and performance metrics
|
| 8 |
+
- **Latest News Analysis**: Get recent news headlines and sentiment analysis
|
| 9 |
+
- **AI-Powered Insights**: Receive comprehensive analysis and investment recommendations
|
| 10 |
+
- **Interactive Chat Interface**: Modern Streamlit-based web interface
|
| 11 |
+
- **Multiple Stock Support**: Analyze AAPL, TSLA, MSFT, GOOG, and more
|
| 12 |
+
|
| 13 |
+
## Setup
|
| 14 |
+
|
| 15 |
+
1. **Install dependencies**:
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
uv sync
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
2. **Create a `.env` file** with your API keys:
|
| 22 |
+
|
| 23 |
+
```
|
| 24 |
+
OPENROUTER_API_KEY=your_openrouter_api_key_here
|
| 25 |
+
MODEL=openai/gpt-4o-mini
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
3. **Run the Streamlit app**:
|
| 29 |
+
```bash
|
| 30 |
+
streamlit run streamlit_app.py
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
## Usage
|
| 34 |
+
|
| 35 |
+
1. Open the web interface in your browser
|
| 36 |
+
2. Select a stock ticker from the dropdown in the sidebar
|
| 37 |
+
3. Start chatting with the financial agent about the selected stock
|
| 38 |
+
4. Ask questions like:
|
| 39 |
+
- "How is this stock performing?"
|
| 40 |
+
- "What's the latest news about this company?"
|
| 41 |
+
- "Should I invest in this stock?"
|
| 42 |
+
- "What are the recent trends?"
|
| 43 |
+
|
| 44 |
+
## Architecture
|
| 45 |
+
|
| 46 |
+
- **Frontend**: Streamlit web interface
|
| 47 |
+
- **Backend**: Python with OpenAI/OpenRouter integration
|
| 48 |
+
- **Data Sources**:
|
| 49 |
+
- Stock data via `yfinance`
|
| 50 |
+
- News data via `gnews`
|
| 51 |
+
- **AI Model**: GPT-4o-mini via OpenRouter
|
| 52 |
+
|
| 53 |
+
## Files
|
| 54 |
+
|
| 55 |
+
- `streamlit_app.py`: Main Streamlit web application
|
| 56 |
+
- `agent_client.py`: Original terminal-based client
|
| 57 |
+
- `stock_data_server.py`: MCP server for stock data
|
| 58 |
+
- `news_server.py`: MCP server for news data
|
main.py → agent_client.py
RENAMED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
# agent_client.py
|
| 2 |
import os
|
| 3 |
import asyncio
|
| 4 |
import json
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import asyncio
|
| 3 |
import json
|
pyproject.toml
CHANGED
|
@@ -11,8 +11,10 @@ dependencies = [
|
|
| 11 |
"mcp[cli]>=1.12.2",
|
| 12 |
"openai>=1.97.1",
|
| 13 |
"pandas>=2.3.1",
|
|
|
|
| 14 |
"python-dotenv>=1.1.1",
|
| 15 |
"sentence-transformers>=5.0.0",
|
|
|
|
| 16 |
"transformers[torch]>=4.54.0",
|
| 17 |
"yfinance>=0.2.65",
|
| 18 |
]
|
|
|
|
| 11 |
"mcp[cli]>=1.12.2",
|
| 12 |
"openai>=1.97.1",
|
| 13 |
"pandas>=2.3.1",
|
| 14 |
+
"plotly>=5.17.0",
|
| 15 |
"python-dotenv>=1.1.1",
|
| 16 |
"sentence-transformers>=5.0.0",
|
| 17 |
+
"streamlit>=1.28.0",
|
| 18 |
"transformers[torch]>=4.54.0",
|
| 19 |
"yfinance>=0.2.65",
|
| 20 |
]
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
beautifulsoup4>=4.13.4
|
| 2 |
+
fastmcp>=2.10.6
|
| 3 |
+
gnews>=0.4.1
|
| 4 |
+
mcp[cli]>=1.12.2
|
| 5 |
+
openai>=1.97.1
|
| 6 |
+
pandas>=2.3.1
|
| 7 |
+
plotly>=5.17.0
|
| 8 |
+
python-dotenv>=1.1.1
|
| 9 |
+
sentence-transformers>=5.0.0
|
| 10 |
+
streamlit>=1.28.0
|
| 11 |
+
transformers[torch]>=4.54.0
|
| 12 |
+
yfinance>=0.2.65
|
simple_mcp_test.py
DELETED
|
@@ -1,44 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Simple MCP test based on official documentation
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import asyncio
|
| 7 |
-
from mcp.client.session import ClientSession
|
| 8 |
-
from mcp.client.stdio import stdio_client
|
| 9 |
-
from mcp import StdioServerParameters
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
async def test_simple_mcp():
|
| 13 |
-
"""Test MCP connection using the official approach"""
|
| 14 |
-
print("Testing MCP connection...")
|
| 15 |
-
|
| 16 |
-
try:
|
| 17 |
-
# Use the official StdioServerParameters approach
|
| 18 |
-
server_params = StdioServerParameters(command="python", args=["news_server.py"])
|
| 19 |
-
|
| 20 |
-
# Connect using the official method
|
| 21 |
-
async with stdio_client(server_params) as (read, write):
|
| 22 |
-
async with ClientSession(read, write) as session:
|
| 23 |
-
await session.initialize()
|
| 24 |
-
print("✅ Session initialized")
|
| 25 |
-
|
| 26 |
-
# List available tools
|
| 27 |
-
tools_response = await session.list_tools()
|
| 28 |
-
print(
|
| 29 |
-
f"✅ Available tools: {[tool.name for tool in tools_response.tools]}"
|
| 30 |
-
)
|
| 31 |
-
|
| 32 |
-
# Call a tool
|
| 33 |
-
result = await session.call_tool("get_latest_news", {"ticker": "TSLA"})
|
| 34 |
-
print(f"✅ Tool result: {result.content[0].text[:100]}...")
|
| 35 |
-
|
| 36 |
-
except Exception as e:
|
| 37 |
-
print(f"❌ Error: {e}")
|
| 38 |
-
import traceback
|
| 39 |
-
|
| 40 |
-
traceback.print_exc()
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
if __name__ == "__main__":
|
| 44 |
-
asyncio.run(test_simple_mcp())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
streamlit_app.py
ADDED
|
@@ -0,0 +1,714 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import asyncio
|
| 3 |
+
import json
|
| 4 |
+
import re
|
| 5 |
+
import os
|
| 6 |
+
import pandas as pd
|
| 7 |
+
import plotly.graph_objects as go
|
| 8 |
+
from plotly.subplots import make_subplots
|
| 9 |
+
import yfinance as yf
|
| 10 |
+
from datetime import datetime, timedelta
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
from openai import OpenAI
|
| 13 |
+
from mcp.client.session import ClientSession
|
| 14 |
+
from mcp.client.stdio import stdio_client
|
| 15 |
+
from mcp import StdioServerParameters, types
|
| 16 |
+
|
| 17 |
+
# Load environment variables
|
| 18 |
+
load_dotenv()
|
| 19 |
+
|
| 20 |
+
# Check if API key exists - support both .env and Streamlit secrets
|
| 21 |
+
api_key = os.getenv("OPENROUTER_API_KEY") or st.secrets.get("OPENROUTER_API_KEY")
|
| 22 |
+
model = os.getenv("MODEL") or st.secrets.get("MODEL")
|
| 23 |
+
|
| 24 |
+
if not api_key:
|
| 25 |
+
st.error(
|
| 26 |
+
"❌ Error: OPENROUTER_API_KEY not found. Please set it in your environment variables or Streamlit secrets."
|
| 27 |
+
)
|
| 28 |
+
st.stop()
|
| 29 |
+
|
| 30 |
+
if not model:
|
| 31 |
+
st.error(
|
| 32 |
+
"❌ Error: MODEL not found. Please set it in your environment variables or Streamlit secrets."
|
| 33 |
+
)
|
| 34 |
+
st.stop()
|
| 35 |
+
|
| 36 |
+
# Configure the client to connect to OpenRouter
|
| 37 |
+
client = OpenAI(
|
| 38 |
+
base_url="https://openrouter.ai/api/v1",
|
| 39 |
+
api_key=api_key,
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
# Global variable to store discovered tools
|
| 43 |
+
discovered_tools = []
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def extract_ticker_from_query(query: str) -> str:
|
| 47 |
+
"""Extract ticker symbol from user query."""
|
| 48 |
+
query_upper = query.upper()
|
| 49 |
+
|
| 50 |
+
# First try to find ticker in parentheses
|
| 51 |
+
paren_match = re.search(r"\(([A-Z]{1,5})\)", query_upper)
|
| 52 |
+
if paren_match:
|
| 53 |
+
return paren_match.group(1)
|
| 54 |
+
|
| 55 |
+
# Look for our predefined tickers in the query
|
| 56 |
+
predefined_tickers = ["AAPL", "TSLA", "MSFT", "GOOG"]
|
| 57 |
+
for ticker in predefined_tickers:
|
| 58 |
+
if ticker in query_upper:
|
| 59 |
+
return ticker
|
| 60 |
+
|
| 61 |
+
# Try to find any 2-5 letter uppercase sequence that might be a ticker
|
| 62 |
+
ticker_match = re.search(r"\b([A-Z]{2,5})\b", query_upper)
|
| 63 |
+
if ticker_match:
|
| 64 |
+
potential_ticker = ticker_match.group(1)
|
| 65 |
+
# Avoid common words that might be mistaken for tickers
|
| 66 |
+
if potential_ticker not in [
|
| 67 |
+
"THE",
|
| 68 |
+
"AND",
|
| 69 |
+
"FOR",
|
| 70 |
+
"HOW",
|
| 71 |
+
"WHAT",
|
| 72 |
+
"WHEN",
|
| 73 |
+
"WHERE",
|
| 74 |
+
"WHY",
|
| 75 |
+
]:
|
| 76 |
+
return potential_ticker
|
| 77 |
+
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def validate_ticker(ticker: str) -> bool:
|
| 82 |
+
"""Validate if ticker symbol is in correct format."""
|
| 83 |
+
if not ticker:
|
| 84 |
+
return False
|
| 85 |
+
# Basic validation: 1-5 uppercase letters
|
| 86 |
+
return bool(re.match(r"^[A-Z]{1,5}$", ticker))
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
async def get_news_data(ticker: str) -> str:
|
| 90 |
+
"""Get news data by calling the news server via MCP."""
|
| 91 |
+
try:
|
| 92 |
+
# Validate ticker
|
| 93 |
+
if not validate_ticker(ticker):
|
| 94 |
+
return f"Invalid ticker symbol: {ticker}. Please use a valid stock symbol (e.g., AAPL, TSLA)."
|
| 95 |
+
|
| 96 |
+
# Set up MCP server parameters
|
| 97 |
+
import os
|
| 98 |
+
import sys
|
| 99 |
+
|
| 100 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
| 101 |
+
news_server_path = os.path.join(current_dir, "news_server.py")
|
| 102 |
+
|
| 103 |
+
if not os.path.exists(news_server_path):
|
| 104 |
+
return f"Error: news_server.py not found at {news_server_path}"
|
| 105 |
+
|
| 106 |
+
# Use the same Python executable as the current process
|
| 107 |
+
python_executable = sys.executable
|
| 108 |
+
server_params = StdioServerParameters(
|
| 109 |
+
command=python_executable, args=[news_server_path]
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
# Connect to the MCP server
|
| 113 |
+
try:
|
| 114 |
+
async with stdio_client(server_params) as (read, write):
|
| 115 |
+
async with ClientSession(read, write) as session:
|
| 116 |
+
# Initialize the session
|
| 117 |
+
await session.initialize()
|
| 118 |
+
|
| 119 |
+
# Call the get_latest_news tool
|
| 120 |
+
with st.status(
|
| 121 |
+
f"🔍 Fetching news data for {ticker}...", expanded=False
|
| 122 |
+
) as status:
|
| 123 |
+
try:
|
| 124 |
+
result = await asyncio.wait_for(
|
| 125 |
+
session.call_tool(
|
| 126 |
+
"get_latest_news", {"ticker": ticker}
|
| 127 |
+
),
|
| 128 |
+
timeout=30.0, # 30 second timeout
|
| 129 |
+
)
|
| 130 |
+
status.update(
|
| 131 |
+
label=f"✅ News data fetched for {ticker}",
|
| 132 |
+
state="complete",
|
| 133 |
+
)
|
| 134 |
+
except asyncio.TimeoutError:
|
| 135 |
+
status.update(
|
| 136 |
+
label="❌ News data fetch timed out", state="error"
|
| 137 |
+
)
|
| 138 |
+
return f"Timeout getting news for {ticker}"
|
| 139 |
+
except Exception as e:
|
| 140 |
+
status.update(
|
| 141 |
+
label=f"❌ Error fetching news: {e}", state="error"
|
| 142 |
+
)
|
| 143 |
+
return f"Error getting news for {ticker}: {e}"
|
| 144 |
+
|
| 145 |
+
# Parse the result properly
|
| 146 |
+
if result.content:
|
| 147 |
+
for content in result.content:
|
| 148 |
+
if isinstance(content, types.TextContent):
|
| 149 |
+
return content.text
|
| 150 |
+
|
| 151 |
+
return f"No news data returned for {ticker}"
|
| 152 |
+
except Exception as e:
|
| 153 |
+
st.error(f"❌ Failed to connect to news server: {e}")
|
| 154 |
+
return f"Failed to connect to news server: {e}"
|
| 155 |
+
|
| 156 |
+
except Exception as e:
|
| 157 |
+
return f"Error getting news for {ticker}: {e}"
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
async def get_stock_data(ticker: str) -> str:
|
| 161 |
+
"""Get stock data by calling the stock server via MCP."""
|
| 162 |
+
try:
|
| 163 |
+
# Validate ticker
|
| 164 |
+
if not validate_ticker(ticker):
|
| 165 |
+
return f"Invalid ticker symbol: {ticker}. Please use a valid stock symbol (e.g., AAPL, TSLA)."
|
| 166 |
+
|
| 167 |
+
# Set up MCP server parameters
|
| 168 |
+
import os
|
| 169 |
+
import sys
|
| 170 |
+
|
| 171 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
| 172 |
+
stock_server_path = os.path.join(current_dir, "stock_data_server.py")
|
| 173 |
+
|
| 174 |
+
if not os.path.exists(stock_server_path):
|
| 175 |
+
return f"Error: stock_data_server.py not found at {stock_server_path}"
|
| 176 |
+
|
| 177 |
+
# Use the same Python executable as the current process
|
| 178 |
+
python_executable = sys.executable
|
| 179 |
+
server_params = StdioServerParameters(
|
| 180 |
+
command=python_executable, args=[stock_server_path]
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
# Connect to the MCP server
|
| 184 |
+
try:
|
| 185 |
+
async with stdio_client(server_params) as (read, write):
|
| 186 |
+
async with ClientSession(read, write) as session:
|
| 187 |
+
# Initialize the session
|
| 188 |
+
await session.initialize()
|
| 189 |
+
|
| 190 |
+
# Call the get_historical_stock_data tool
|
| 191 |
+
with st.status(
|
| 192 |
+
f"📊 Fetching stock data for {ticker}...", expanded=False
|
| 193 |
+
) as status:
|
| 194 |
+
try:
|
| 195 |
+
result = await asyncio.wait_for(
|
| 196 |
+
session.call_tool(
|
| 197 |
+
"get_historical_stock_data", {"ticker": ticker}
|
| 198 |
+
),
|
| 199 |
+
timeout=30.0, # 30 second timeout
|
| 200 |
+
)
|
| 201 |
+
status.update(
|
| 202 |
+
label=f"✅ Stock data fetched for {ticker}",
|
| 203 |
+
state="complete",
|
| 204 |
+
)
|
| 205 |
+
except asyncio.TimeoutError:
|
| 206 |
+
status.update(
|
| 207 |
+
label="❌ Stock data fetch timed out", state="error"
|
| 208 |
+
)
|
| 209 |
+
return f"Timeout getting stock data for {ticker}"
|
| 210 |
+
except Exception as e:
|
| 211 |
+
status.update(
|
| 212 |
+
label=f"❌ Error fetching stock data: {e}",
|
| 213 |
+
state="error",
|
| 214 |
+
)
|
| 215 |
+
return f"Error getting stock data for {ticker}: {e}"
|
| 216 |
+
|
| 217 |
+
# Parse the result properly
|
| 218 |
+
if result.content:
|
| 219 |
+
for content in result.content:
|
| 220 |
+
if isinstance(content, types.TextContent):
|
| 221 |
+
return content.text
|
| 222 |
+
|
| 223 |
+
return f"No stock data returned for {ticker}"
|
| 224 |
+
except Exception as e:
|
| 225 |
+
st.error(f"❌ Failed to connect to stock data server: {e}")
|
| 226 |
+
return f"Failed to connect to stock data server: {e}"
|
| 227 |
+
|
| 228 |
+
except Exception as e:
|
| 229 |
+
return f"Error getting stock data for {ticker}: {e}"
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def create_stock_chart(ticker: str):
|
| 233 |
+
"""Create an interactive stock price chart for the given ticker."""
|
| 234 |
+
try:
|
| 235 |
+
# Get stock data
|
| 236 |
+
stock = yf.Ticker(ticker)
|
| 237 |
+
hist_data = stock.history(period="30d")
|
| 238 |
+
|
| 239 |
+
if hist_data.empty:
|
| 240 |
+
st.warning(f"No data available for {ticker}")
|
| 241 |
+
return
|
| 242 |
+
|
| 243 |
+
# Create simple line chart
|
| 244 |
+
fig = go.Figure()
|
| 245 |
+
|
| 246 |
+
# Add price line chart
|
| 247 |
+
fig.add_trace(
|
| 248 |
+
go.Scatter(
|
| 249 |
+
x=hist_data.index,
|
| 250 |
+
y=hist_data["Close"],
|
| 251 |
+
mode="lines+markers",
|
| 252 |
+
name=f"{ticker} Price",
|
| 253 |
+
line=dict(color="#1f77b4", width=2),
|
| 254 |
+
marker=dict(size=4),
|
| 255 |
+
)
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
# Update layout
|
| 259 |
+
fig.update_layout(
|
| 260 |
+
title=f"{ticker} Stock Price (30 Days)",
|
| 261 |
+
xaxis_title="Date",
|
| 262 |
+
yaxis_title="Price ($)",
|
| 263 |
+
height=500,
|
| 264 |
+
showlegend=False,
|
| 265 |
+
hovermode="x unified",
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
# Update axes
|
| 269 |
+
fig.update_xaxes(
|
| 270 |
+
title_text="Date",
|
| 271 |
+
tickformat="%b %d",
|
| 272 |
+
tickangle=45,
|
| 273 |
+
)
|
| 274 |
+
fig.update_yaxes(title_text="Price ($)")
|
| 275 |
+
|
| 276 |
+
return fig
|
| 277 |
+
|
| 278 |
+
except Exception as e:
|
| 279 |
+
st.error(f"Error creating chart for {ticker}: {e}")
|
| 280 |
+
return None
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def initialize_tools():
|
| 284 |
+
"""Initialize the available tools."""
|
| 285 |
+
global discovered_tools
|
| 286 |
+
|
| 287 |
+
discovered_tools = [
|
| 288 |
+
{
|
| 289 |
+
"type": "function",
|
| 290 |
+
"function": {
|
| 291 |
+
"name": "get_latest_news",
|
| 292 |
+
"description": "Fetches recent news headlines and descriptions for a specific stock ticker. Use this when user asks about news, updates, or recent events about a company.",
|
| 293 |
+
"parameters": {
|
| 294 |
+
"type": "object",
|
| 295 |
+
"properties": {
|
| 296 |
+
"ticker": {
|
| 297 |
+
"type": "string",
|
| 298 |
+
"description": "The stock ticker symbol (e.g., 'AAPL', 'GOOG', 'TSLA'). Must be a valid stock symbol.",
|
| 299 |
+
}
|
| 300 |
+
},
|
| 301 |
+
"required": ["ticker"],
|
| 302 |
+
},
|
| 303 |
+
},
|
| 304 |
+
},
|
| 305 |
+
{
|
| 306 |
+
"type": "function",
|
| 307 |
+
"function": {
|
| 308 |
+
"name": "get_historical_stock_data",
|
| 309 |
+
"description": "Fetches recent historical stock data (Open, High, Low, Close, Volume) for a given ticker. Use this when user asks about stock performance, price data, or market performance.",
|
| 310 |
+
"parameters": {
|
| 311 |
+
"type": "object",
|
| 312 |
+
"properties": {
|
| 313 |
+
"ticker": {
|
| 314 |
+
"type": "string",
|
| 315 |
+
"description": "The stock ticker symbol (e.g., 'AAPL', 'TSLA', 'MSFT'). Must be a valid stock symbol.",
|
| 316 |
+
}
|
| 317 |
+
},
|
| 318 |
+
"required": ["ticker"],
|
| 319 |
+
},
|
| 320 |
+
},
|
| 321 |
+
},
|
| 322 |
+
]
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
async def execute_tool_call(tool_call):
|
| 326 |
+
"""Execute a tool call using MCP servers."""
|
| 327 |
+
try:
|
| 328 |
+
tool_name = tool_call.function.name
|
| 329 |
+
arguments = json.loads(tool_call.function.arguments)
|
| 330 |
+
ticker = arguments.get("ticker")
|
| 331 |
+
|
| 332 |
+
with st.status(
|
| 333 |
+
f"🛠️ Executing {tool_name} for {ticker}...", expanded=False
|
| 334 |
+
) as status:
|
| 335 |
+
if tool_name == "get_latest_news":
|
| 336 |
+
result = await get_news_data(ticker)
|
| 337 |
+
if "Error" in result or "Failed" in result:
|
| 338 |
+
status.update(label=f"❌ {result}", state="error")
|
| 339 |
+
else:
|
| 340 |
+
status.update(
|
| 341 |
+
label=f"✅ {tool_name} completed for {ticker}", state="complete"
|
| 342 |
+
)
|
| 343 |
+
return result
|
| 344 |
+
elif tool_name == "get_historical_stock_data":
|
| 345 |
+
result = await get_stock_data(ticker)
|
| 346 |
+
if "Error" in result or "Failed" in result:
|
| 347 |
+
status.update(label=f"❌ {result}", state="error")
|
| 348 |
+
else:
|
| 349 |
+
status.update(
|
| 350 |
+
label=f"✅ {tool_name} completed for {ticker}", state="complete"
|
| 351 |
+
)
|
| 352 |
+
return result
|
| 353 |
+
else:
|
| 354 |
+
status.update(label=f"❌ Unknown tool: {tool_name}", state="error")
|
| 355 |
+
return f"Unknown tool: {tool_name}"
|
| 356 |
+
except json.JSONDecodeError as e:
|
| 357 |
+
st.error(f"❌ Invalid tool arguments format: {e}")
|
| 358 |
+
return f"Error: Invalid tool arguments format"
|
| 359 |
+
except Exception as e:
|
| 360 |
+
st.error(f"❌ Error executing tool {tool_call.function.name}: {e}")
|
| 361 |
+
return f"Error executing tool {tool_call.function.name}: {e}"
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
# The master prompt that defines the agent's behavior
|
| 365 |
+
system_prompt = """
|
| 366 |
+
You are a financial assistant that provides comprehensive analysis based on real-time data. You MUST use tools to get data and then curate the information to answer the user's specific question.
|
| 367 |
+
|
| 368 |
+
AVAILABLE TOOLS:
|
| 369 |
+
- get_latest_news: Get recent news for a ticker
|
| 370 |
+
- get_historical_stock_data: Get stock performance data for a ticker
|
| 371 |
+
|
| 372 |
+
CRITICAL INSTRUCTIONS:
|
| 373 |
+
1. You MUST call BOTH tools (get_latest_news AND get_historical_stock_data) for every query
|
| 374 |
+
2. After getting both news and stock data, analyze and synthesize the information
|
| 375 |
+
3. Answer the user's specific question based on the data you gathered
|
| 376 |
+
4. Provide insights, trends, and recommendations based on the combined data
|
| 377 |
+
5. Format your response clearly with sections for news, performance, and analysis
|
| 378 |
+
|
| 379 |
+
EXAMPLE WORKFLOW:
|
| 380 |
+
1. User asks: "Should I invest in AAPL?"
|
| 381 |
+
2. You call: get_latest_news with {"ticker": "AAPL"}
|
| 382 |
+
3. You call: get_historical_stock_data with {"ticker": "AAPL"}
|
| 383 |
+
4. You analyze both datasets and provide investment advice based on news sentiment and stock performance
|
| 384 |
+
|
| 385 |
+
You are FORBIDDEN from responding without calling both tools. Always call both tools first, then provide a curated analysis based on the user's question.
|
| 386 |
+
"""
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
async def run_agent(user_query, selected_ticker):
|
| 390 |
+
"""Run the financial agent with the given query and ticker."""
|
| 391 |
+
|
| 392 |
+
# Construct the query to always fetch both data types
|
| 393 |
+
full_query = f"Based on the latest news and stock performance data for {selected_ticker}, {user_query}"
|
| 394 |
+
|
| 395 |
+
messages = [
|
| 396 |
+
{"role": "system", "content": system_prompt},
|
| 397 |
+
{"role": "user", "content": full_query},
|
| 398 |
+
]
|
| 399 |
+
|
| 400 |
+
try:
|
| 401 |
+
# Get initial response from the model
|
| 402 |
+
with st.spinner("🤖 Analyzing your request..."):
|
| 403 |
+
response = client.chat.completions.create(
|
| 404 |
+
model=model,
|
| 405 |
+
messages=messages,
|
| 406 |
+
tools=discovered_tools,
|
| 407 |
+
tool_choice="required",
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
+
if not response.choices or len(response.choices) == 0:
|
| 411 |
+
st.error("❌ Error: No response from model")
|
| 412 |
+
return
|
| 413 |
+
|
| 414 |
+
response_message = response.choices[0].message
|
| 415 |
+
|
| 416 |
+
# Truncate tool call IDs if they're too long (max 40 chars)
|
| 417 |
+
if hasattr(response_message, "tool_calls") and response_message.tool_calls:
|
| 418 |
+
for tool_call in response_message.tool_calls:
|
| 419 |
+
if len(tool_call.id) > 40:
|
| 420 |
+
tool_call.id = tool_call.id[:40]
|
| 421 |
+
|
| 422 |
+
messages.append(response_message)
|
| 423 |
+
|
| 424 |
+
# Execute tool calls if any
|
| 425 |
+
if response_message.tool_calls:
|
| 426 |
+
st.info("🛠️ Executing data collection...")
|
| 427 |
+
for tool_call in response_message.tool_calls:
|
| 428 |
+
# Execute the tool call
|
| 429 |
+
tool_result = await execute_tool_call(tool_call)
|
| 430 |
+
|
| 431 |
+
# Add tool result to messages
|
| 432 |
+
messages.append(
|
| 433 |
+
{
|
| 434 |
+
"role": "tool",
|
| 435 |
+
"tool_call_id": tool_call.id[:40], # Truncate to max 40 chars
|
| 436 |
+
"content": tool_result if tool_result else "No data available",
|
| 437 |
+
}
|
| 438 |
+
)
|
| 439 |
+
|
| 440 |
+
# Get final response from the model
|
| 441 |
+
with st.spinner("🤖 Generating analysis..."):
|
| 442 |
+
final_response = client.chat.completions.create(
|
| 443 |
+
model="openai/gpt-4o-mini", # Try a different model
|
| 444 |
+
messages=messages,
|
| 445 |
+
)
|
| 446 |
+
|
| 447 |
+
if final_response.choices and len(final_response.choices) > 0:
|
| 448 |
+
final_content = final_response.choices[0].message.content
|
| 449 |
+
return final_content if final_content else "Empty response"
|
| 450 |
+
else:
|
| 451 |
+
return "No response generated"
|
| 452 |
+
else:
|
| 453 |
+
return (
|
| 454 |
+
response_message.content if response_message.content else "No response"
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
except Exception as e:
|
| 458 |
+
st.error(f"❌ Error: {e}")
|
| 459 |
+
return "Please try again with a different question."
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def display_top_news(ticker: str):
|
| 463 |
+
"""Display top news headlines for the given ticker with clickable links."""
|
| 464 |
+
try:
|
| 465 |
+
import gnews
|
| 466 |
+
from bs4 import BeautifulSoup
|
| 467 |
+
import re
|
| 468 |
+
|
| 469 |
+
def preprocess_text(text):
|
| 470 |
+
"""A simple function to clean text by removing HTML and extra whitespace."""
|
| 471 |
+
if not text:
|
| 472 |
+
return ""
|
| 473 |
+
soup = BeautifulSoup(text, "html.parser")
|
| 474 |
+
clean_text = soup.get_text()
|
| 475 |
+
clean_text = re.sub(r"\s+", " ", clean_text).strip()
|
| 476 |
+
return clean_text
|
| 477 |
+
|
| 478 |
+
# Get news data
|
| 479 |
+
google_news = gnews.GNews(language="en", country="US", period="7d")
|
| 480 |
+
search_query = f'"{ticker}" stock market news'
|
| 481 |
+
articles = google_news.get_news(search_query)
|
| 482 |
+
|
| 483 |
+
if not articles:
|
| 484 |
+
st.info(f"No recent news found for {ticker}")
|
| 485 |
+
return
|
| 486 |
+
|
| 487 |
+
# Display top 5 articles
|
| 488 |
+
for i, article in enumerate(articles[:5], 1):
|
| 489 |
+
title = preprocess_text(article.get("title", ""))
|
| 490 |
+
url = article.get("url", "")
|
| 491 |
+
publisher = article.get("publisher", {}).get("title", "Unknown Source")
|
| 492 |
+
|
| 493 |
+
# Create a clickable link
|
| 494 |
+
if url:
|
| 495 |
+
st.markdown(f"[{title}]({url})")
|
| 496 |
+
st.caption(f"Source: {publisher}")
|
| 497 |
+
else:
|
| 498 |
+
st.markdown(f"{title}")
|
| 499 |
+
st.caption(f"Source: {publisher}")
|
| 500 |
+
|
| 501 |
+
# Add some spacing between articles
|
| 502 |
+
if i < 5:
|
| 503 |
+
st.markdown("---")
|
| 504 |
+
|
| 505 |
+
except Exception as e:
|
| 506 |
+
st.error(f"Error fetching news for {ticker}: {e}")
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
def test_server_availability():
|
| 510 |
+
"""Test if the MCP servers are available and can be executed."""
|
| 511 |
+
import os
|
| 512 |
+
import subprocess
|
| 513 |
+
import time
|
| 514 |
+
|
| 515 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
| 516 |
+
|
| 517 |
+
# Test news server
|
| 518 |
+
news_server_path = os.path.join(current_dir, "news_server.py")
|
| 519 |
+
if not os.path.exists(news_server_path):
|
| 520 |
+
st.error(f"❌ news_server.py not found at {news_server_path}")
|
| 521 |
+
return False
|
| 522 |
+
|
| 523 |
+
# Test stock data server
|
| 524 |
+
stock_server_path = os.path.join(current_dir, "stock_data_server.py")
|
| 525 |
+
if not os.path.exists(stock_server_path):
|
| 526 |
+
st.error(f"❌ stock_data_server.py not found at {stock_server_path}")
|
| 527 |
+
return False
|
| 528 |
+
|
| 529 |
+
# Test if servers can be executed by checking if they can be imported
|
| 530 |
+
import sys
|
| 531 |
+
import importlib.util
|
| 532 |
+
|
| 533 |
+
# Initialize session state for notifications
|
| 534 |
+
if "notifications" not in st.session_state:
|
| 535 |
+
st.session_state.notifications = []
|
| 536 |
+
if "notification_times" not in st.session_state:
|
| 537 |
+
st.session_state.notification_times = {}
|
| 538 |
+
if "servers_importable_shown" not in st.session_state:
|
| 539 |
+
st.session_state.servers_importable_shown = False
|
| 540 |
+
|
| 541 |
+
current_time = time.time()
|
| 542 |
+
|
| 543 |
+
# Clean up old notifications (older than 10 seconds)
|
| 544 |
+
st.session_state.notifications = [
|
| 545 |
+
msg
|
| 546 |
+
for msg, timestamp in zip(
|
| 547 |
+
st.session_state.notifications, st.session_state.notification_times.values()
|
| 548 |
+
)
|
| 549 |
+
if current_time - timestamp < 10
|
| 550 |
+
]
|
| 551 |
+
st.session_state.notification_times = {
|
| 552 |
+
k: v
|
| 553 |
+
for k, v in st.session_state.notification_times.items()
|
| 554 |
+
if current_time - v < 10
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
try:
|
| 558 |
+
# Test if news_server can be imported
|
| 559 |
+
spec = importlib.util.spec_from_file_location("news_server", news_server_path)
|
| 560 |
+
if spec is None or spec.loader is None:
|
| 561 |
+
st.warning("⚠️ Could not load news_server.py")
|
| 562 |
+
else:
|
| 563 |
+
# Add temporary success notification only once
|
| 564 |
+
if not st.session_state.servers_importable_shown:
|
| 565 |
+
st.success("✅ news_server.py is importable")
|
| 566 |
+
st.session_state.servers_importable_shown = True
|
| 567 |
+
except Exception as e:
|
| 568 |
+
st.warning(f"⚠️ Could not import news_server.py: {e}")
|
| 569 |
+
|
| 570 |
+
try:
|
| 571 |
+
# Test if stock_data_server can be imported
|
| 572 |
+
spec = importlib.util.spec_from_file_location(
|
| 573 |
+
"stock_data_server", stock_server_path
|
| 574 |
+
)
|
| 575 |
+
if spec is None or spec.loader is None:
|
| 576 |
+
st.warning("⚠️ Could not load stock_data_server.py")
|
| 577 |
+
else:
|
| 578 |
+
# Add temporary success notification only once
|
| 579 |
+
if not st.session_state.servers_importable_shown:
|
| 580 |
+
st.success("✅ stock_data_server.py is importable")
|
| 581 |
+
st.session_state.servers_importable_shown = True
|
| 582 |
+
except Exception as e:
|
| 583 |
+
st.warning(f"⚠️ Could not import stock_data_server.py: {e}")
|
| 584 |
+
|
| 585 |
+
return True
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
def main():
|
| 589 |
+
st.set_page_config(page_title="Financial Agent", page_icon="📈", layout="wide")
|
| 590 |
+
|
| 591 |
+
st.title("📈 Financial Agent")
|
| 592 |
+
st.markdown(
|
| 593 |
+
"Get comprehensive financial analysis and insights for your selected stocks."
|
| 594 |
+
)
|
| 595 |
+
|
| 596 |
+
# Initialize tools
|
| 597 |
+
initialize_tools()
|
| 598 |
+
|
| 599 |
+
# Test server availability only once on startup
|
| 600 |
+
if "servers_tested" not in st.session_state:
|
| 601 |
+
st.session_state.servers_tested = False
|
| 602 |
+
|
| 603 |
+
if not st.session_state.servers_tested:
|
| 604 |
+
test_server_availability()
|
| 605 |
+
st.session_state.servers_tested = True
|
| 606 |
+
|
| 607 |
+
# Available tickers
|
| 608 |
+
available_tickers = {
|
| 609 |
+
"AAPL": "Apple Inc.",
|
| 610 |
+
"TSLA": "Tesla Inc.",
|
| 611 |
+
"MSFT": "Microsoft Corporation",
|
| 612 |
+
"GOOG": "Alphabet Inc. (Google)",
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
# Sidebar for ticker selection
|
| 616 |
+
st.sidebar.header("📊 Stock Selection")
|
| 617 |
+
selected_ticker = st.sidebar.selectbox(
|
| 618 |
+
"Choose a stock ticker:",
|
| 619 |
+
options=list(available_tickers.keys()),
|
| 620 |
+
format_func=lambda x: f"{x} - {available_tickers[x]}",
|
| 621 |
+
index=None,
|
| 622 |
+
placeholder="Select a ticker...",
|
| 623 |
+
)
|
| 624 |
+
|
| 625 |
+
# Main content area
|
| 626 |
+
if not selected_ticker:
|
| 627 |
+
st.info(
|
| 628 |
+
"👈 Please select a stock ticker from the sidebar to view the chart and start chatting."
|
| 629 |
+
)
|
| 630 |
+
st.markdown(
|
| 631 |
+
"""
|
| 632 |
+
**How to use:**
|
| 633 |
+
1. Select a stock ticker from the sidebar
|
| 634 |
+
2. View the interactive stock price chart
|
| 635 |
+
3. Ask questions about the stock's performance, news, or investment advice
|
| 636 |
+
4. The agent will fetch real-time data and provide comprehensive analysis
|
| 637 |
+
|
| 638 |
+
**Example questions:**
|
| 639 |
+
- "How is this stock performing?"
|
| 640 |
+
- "What's the latest news about this company?"
|
| 641 |
+
- "Should I invest in this stock?"
|
| 642 |
+
- "What are the recent trends?"
|
| 643 |
+
"""
|
| 644 |
+
)
|
| 645 |
+
else:
|
| 646 |
+
st.success(
|
| 647 |
+
f"✅ Selected: {selected_ticker} - {available_tickers[selected_ticker]}"
|
| 648 |
+
)
|
| 649 |
+
|
| 650 |
+
# Stock Chart and News Section
|
| 651 |
+
st.header("📈 Stock Analysis")
|
| 652 |
+
|
| 653 |
+
# Create two columns for chart and news
|
| 654 |
+
col1, col2 = st.columns([2, 1])
|
| 655 |
+
|
| 656 |
+
with col1:
|
| 657 |
+
st.subheader("📈 Stock Price Chart")
|
| 658 |
+
# Create and display the stock chart
|
| 659 |
+
with st.spinner(f"Loading chart for {selected_ticker}..."):
|
| 660 |
+
chart_fig = create_stock_chart(selected_ticker)
|
| 661 |
+
if chart_fig:
|
| 662 |
+
st.plotly_chart(chart_fig, use_container_width=True)
|
| 663 |
+
else:
|
| 664 |
+
st.warning(f"Could not load chart for {selected_ticker}")
|
| 665 |
+
|
| 666 |
+
with col2:
|
| 667 |
+
st.subheader("📰 Top News")
|
| 668 |
+
# Display top news for the selected ticker
|
| 669 |
+
display_top_news(selected_ticker)
|
| 670 |
+
|
| 671 |
+
# Chat Section in a container
|
| 672 |
+
st.header("💬 Chat with Financial Agent")
|
| 673 |
+
|
| 674 |
+
# Create a container for the chat interface
|
| 675 |
+
with st.container():
|
| 676 |
+
# Initialize chat history
|
| 677 |
+
if "messages" not in st.session_state:
|
| 678 |
+
st.session_state.messages = []
|
| 679 |
+
|
| 680 |
+
# Display chat history
|
| 681 |
+
for message in st.session_state.messages:
|
| 682 |
+
with st.chat_message(message["role"]):
|
| 683 |
+
st.markdown(message["content"])
|
| 684 |
+
|
| 685 |
+
# Chat input
|
| 686 |
+
if prompt := st.chat_input(f"Ask about {selected_ticker}..."):
|
| 687 |
+
# Add user message to chat history
|
| 688 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 689 |
+
|
| 690 |
+
# Display user message
|
| 691 |
+
with st.chat_message("user"):
|
| 692 |
+
st.markdown(prompt)
|
| 693 |
+
|
| 694 |
+
# Display assistant response
|
| 695 |
+
with st.chat_message("assistant"):
|
| 696 |
+
with st.spinner("🤖 Analyzing..."):
|
| 697 |
+
response = asyncio.run(run_agent(prompt, selected_ticker))
|
| 698 |
+
st.markdown(response)
|
| 699 |
+
st.session_state.messages.append(
|
| 700 |
+
{"role": "assistant", "content": response}
|
| 701 |
+
)
|
| 702 |
+
|
| 703 |
+
# Clear chat button
|
| 704 |
+
col1, col2 = st.columns([1, 4])
|
| 705 |
+
with col1:
|
| 706 |
+
if st.button("🗑️ Clear Chat History"):
|
| 707 |
+
st.session_state.messages = []
|
| 708 |
+
st.rerun()
|
| 709 |
+
with col2:
|
| 710 |
+
st.markdown("*Chat history will be maintained during your session*")
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
if __name__ == "__main__":
|
| 714 |
+
main()
|
uv.lock
CHANGED
|
@@ -26,6 +26,22 @@ wheels = [
|
|
| 26 |
{ url = "https://files.pythonhosted.org/packages/9f/1c/a17fb513aeb684fb83bef5f395910f53103ab30308bbdd77fd66d6698c46/accelerate-1.9.0-py3-none-any.whl", hash = "sha256:c24739a97ade1d54af4549a65f8b6b046adc87e2b3e4d6c66516e32c53d5a8f1", size = 367073, upload_time = "2025-07-16T16:24:52.957Z" },
|
| 27 |
]
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
[[package]]
|
| 30 |
name = "annotated-types"
|
| 31 |
version = "0.7.0"
|
|
@@ -84,6 +100,24 @@ wheels = [
|
|
| 84 |
{ url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload_time = "2025-04-15T17:05:12.221Z" },
|
| 85 |
]
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
[[package]]
|
| 88 |
name = "certifi"
|
| 89 |
version = "2025.7.14"
|
|
@@ -429,8 +463,10 @@ dependencies = [
|
|
| 429 |
{ name = "mcp", extra = ["cli"] },
|
| 430 |
{ name = "openai" },
|
| 431 |
{ name = "pandas" },
|
|
|
|
| 432 |
{ name = "python-dotenv" },
|
| 433 |
{ name = "sentence-transformers" },
|
|
|
|
| 434 |
{ name = "transformers", extra = ["torch"] },
|
| 435 |
{ name = "yfinance" },
|
| 436 |
]
|
|
@@ -443,8 +479,10 @@ requires-dist = [
|
|
| 443 |
{ name = "mcp", extras = ["cli"], specifier = ">=1.12.2" },
|
| 444 |
{ name = "openai", specifier = ">=1.97.1" },
|
| 445 |
{ name = "pandas", specifier = ">=2.3.1" },
|
|
|
|
| 446 |
{ name = "python-dotenv", specifier = ">=1.1.1" },
|
| 447 |
{ name = "sentence-transformers", specifier = ">=5.0.0" },
|
|
|
|
| 448 |
{ name = "transformers", extras = ["torch"], specifier = ">=4.54.0" },
|
| 449 |
{ name = "yfinance", specifier = ">=0.2.65" },
|
| 450 |
]
|
|
@@ -477,6 +515,30 @@ wheels = [
|
|
| 477 |
{ url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload_time = "2025-07-15T16:05:19.529Z" },
|
| 478 |
]
|
| 479 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
[[package]]
|
| 481 |
name = "gnews"
|
| 482 |
version = "0.4.1"
|
|
@@ -823,6 +885,15 @@ version = "0.0.12"
|
|
| 823 |
source = { registry = "https://pypi.org/simple" }
|
| 824 |
sdist = { url = "https://files.pythonhosted.org/packages/17/0d/74f0293dfd7dcc3837746d0138cbedd60b31701ecc75caec7d3f281feba0/multitasking-0.0.12.tar.gz", hash = "sha256:2fba2fa8ed8c4b85e227c5dd7dc41c7d658de3b6f247927316175a57349b84d1", size = 19984, upload_time = "2025-07-20T21:27:51.636Z" }
|
| 825 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 826 |
[[package]]
|
| 827 |
name = "networkx"
|
| 828 |
version = "3.4.2"
|
|
@@ -1337,6 +1408,19 @@ wheels = [
|
|
| 1337 |
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload_time = "2025-05-07T22:47:40.376Z" },
|
| 1338 |
]
|
| 1339 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1340 |
[[package]]
|
| 1341 |
name = "protobuf"
|
| 1342 |
version = "6.31.1"
|
|
@@ -1366,6 +1450,49 @@ wheels = [
|
|
| 1366 |
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload_time = "2025-02-13T21:54:37.486Z" },
|
| 1367 |
]
|
| 1368 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1369 |
[[package]]
|
| 1370 |
name = "pycparser"
|
| 1371 |
version = "2.22"
|
|
@@ -1496,6 +1623,20 @@ wheels = [
|
|
| 1496 |
{ url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload_time = "2025-06-24T13:26:45.485Z" },
|
| 1497 |
]
|
| 1498 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1499 |
[[package]]
|
| 1500 |
name = "pygments"
|
| 1501 |
version = "2.19.2"
|
|
@@ -2092,6 +2233,15 @@ wheels = [
|
|
| 2092 |
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload_time = "2024-12-04T17:35:26.475Z" },
|
| 2093 |
]
|
| 2094 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2095 |
[[package]]
|
| 2096 |
name = "sniffio"
|
| 2097 |
version = "1.3.1"
|
|
@@ -2135,6 +2285,36 @@ wheels = [
|
|
| 2135 |
{ url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload_time = "2025-07-20T17:31:56.738Z" },
|
| 2136 |
]
|
| 2137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2138 |
[[package]]
|
| 2139 |
name = "sympy"
|
| 2140 |
version = "1.14.0"
|
|
@@ -2147,6 +2327,15 @@ wheels = [
|
|
| 2147 |
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload_time = "2025-04-27T18:04:59.103Z" },
|
| 2148 |
]
|
| 2149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2150 |
[[package]]
|
| 2151 |
name = "threadpoolctl"
|
| 2152 |
version = "3.6.0"
|
|
@@ -2181,6 +2370,15 @@ wheels = [
|
|
| 2181 |
{ url = "https://files.pythonhosted.org/packages/13/c3/cc2755ee10be859c4338c962a35b9a663788c0c0b50c0bdd8078fb6870cf/tokenizers-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:58747bb898acdb1007f37a7bbe614346e98dc28708ffb66a3fd50ce169ac6c98", size = 2509918, upload_time = "2025-06-24T10:24:53.71Z" },
|
| 2182 |
]
|
| 2183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2184 |
[[package]]
|
| 2185 |
name = "torch"
|
| 2186 |
version = "2.7.1"
|
|
@@ -2233,6 +2431,25 @@ wheels = [
|
|
| 2233 |
{ url = "https://files.pythonhosted.org/packages/b1/29/beb45cdf5c4fc3ebe282bf5eafc8dfd925ead7299b3c97491900fe5ed844/torch-2.7.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:988b0cbc4333618a1056d2ebad9eb10089637b659eb645434d0809d8d937b946", size = 68645708, upload_time = "2025-06-04T17:34:39.852Z" },
|
| 2234 |
]
|
| 2235 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2236 |
[[package]]
|
| 2237 |
name = "tqdm"
|
| 2238 |
version = "4.67.1"
|
|
@@ -2356,6 +2573,24 @@ wheels = [
|
|
| 2356 |
{ url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload_time = "2025-06-28T16:15:44.816Z" },
|
| 2357 |
]
|
| 2358 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2359 |
[[package]]
|
| 2360 |
name = "websockets"
|
| 2361 |
version = "15.0.1"
|
|
|
|
| 26 |
{ url = "https://files.pythonhosted.org/packages/9f/1c/a17fb513aeb684fb83bef5f395910f53103ab30308bbdd77fd66d6698c46/accelerate-1.9.0-py3-none-any.whl", hash = "sha256:c24739a97ade1d54af4549a65f8b6b046adc87e2b3e4d6c66516e32c53d5a8f1", size = 367073, upload_time = "2025-07-16T16:24:52.957Z" },
|
| 27 |
]
|
| 28 |
|
| 29 |
+
[[package]]
|
| 30 |
+
name = "altair"
|
| 31 |
+
version = "5.5.0"
|
| 32 |
+
source = { registry = "https://pypi.org/simple" }
|
| 33 |
+
dependencies = [
|
| 34 |
+
{ name = "jinja2" },
|
| 35 |
+
{ name = "jsonschema" },
|
| 36 |
+
{ name = "narwhals" },
|
| 37 |
+
{ name = "packaging" },
|
| 38 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.14'" },
|
| 39 |
+
]
|
| 40 |
+
sdist = { url = "https://files.pythonhosted.org/packages/16/b1/f2969c7bdb8ad8bbdda031687defdce2c19afba2aa2c8e1d2a17f78376d8/altair-5.5.0.tar.gz", hash = "sha256:d960ebe6178c56de3855a68c47b516be38640b73fb3b5111c2a9ca90546dd73d", size = 705305, upload_time = "2024-11-23T23:39:58.542Z" }
|
| 41 |
+
wheels = [
|
| 42 |
+
{ url = "https://files.pythonhosted.org/packages/aa/f3/0b6ced594e51cc95d8c1fc1640d3623770d01e4969d29c0bd09945fafefa/altair-5.5.0-py3-none-any.whl", hash = "sha256:91a310b926508d560fe0148d02a194f38b824122641ef528113d029fcd129f8c", size = 731200, upload_time = "2024-11-23T23:39:56.4Z" },
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
[[package]]
|
| 46 |
name = "annotated-types"
|
| 47 |
version = "0.7.0"
|
|
|
|
| 100 |
{ url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload_time = "2025-04-15T17:05:12.221Z" },
|
| 101 |
]
|
| 102 |
|
| 103 |
+
[[package]]
|
| 104 |
+
name = "blinker"
|
| 105 |
+
version = "1.9.0"
|
| 106 |
+
source = { registry = "https://pypi.org/simple" }
|
| 107 |
+
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload_time = "2024-11-08T17:25:47.436Z" }
|
| 108 |
+
wheels = [
|
| 109 |
+
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload_time = "2024-11-08T17:25:46.184Z" },
|
| 110 |
+
]
|
| 111 |
+
|
| 112 |
+
[[package]]
|
| 113 |
+
name = "cachetools"
|
| 114 |
+
version = "6.1.0"
|
| 115 |
+
source = { registry = "https://pypi.org/simple" }
|
| 116 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8a/89/817ad5d0411f136c484d535952aef74af9b25e0d99e90cdffbe121e6d628/cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587", size = 30714, upload_time = "2025-06-16T18:51:03.07Z" }
|
| 117 |
+
wheels = [
|
| 118 |
+
{ url = "https://files.pythonhosted.org/packages/00/f0/2ef431fe4141f5e334759d73e81120492b23b2824336883a91ac04ba710b/cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", size = 11189, upload_time = "2025-06-16T18:51:01.514Z" },
|
| 119 |
+
]
|
| 120 |
+
|
| 121 |
[[package]]
|
| 122 |
name = "certifi"
|
| 123 |
version = "2025.7.14"
|
|
|
|
| 463 |
{ name = "mcp", extra = ["cli"] },
|
| 464 |
{ name = "openai" },
|
| 465 |
{ name = "pandas" },
|
| 466 |
+
{ name = "plotly" },
|
| 467 |
{ name = "python-dotenv" },
|
| 468 |
{ name = "sentence-transformers" },
|
| 469 |
+
{ name = "streamlit" },
|
| 470 |
{ name = "transformers", extra = ["torch"] },
|
| 471 |
{ name = "yfinance" },
|
| 472 |
]
|
|
|
|
| 479 |
{ name = "mcp", extras = ["cli"], specifier = ">=1.12.2" },
|
| 480 |
{ name = "openai", specifier = ">=1.97.1" },
|
| 481 |
{ name = "pandas", specifier = ">=2.3.1" },
|
| 482 |
+
{ name = "plotly", specifier = ">=5.17.0" },
|
| 483 |
{ name = "python-dotenv", specifier = ">=1.1.1" },
|
| 484 |
{ name = "sentence-transformers", specifier = ">=5.0.0" },
|
| 485 |
+
{ name = "streamlit", specifier = ">=1.28.0" },
|
| 486 |
{ name = "transformers", extras = ["torch"], specifier = ">=4.54.0" },
|
| 487 |
{ name = "yfinance", specifier = ">=0.2.65" },
|
| 488 |
]
|
|
|
|
| 515 |
{ url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload_time = "2025-07-15T16:05:19.529Z" },
|
| 516 |
]
|
| 517 |
|
| 518 |
+
[[package]]
|
| 519 |
+
name = "gitdb"
|
| 520 |
+
version = "4.0.12"
|
| 521 |
+
source = { registry = "https://pypi.org/simple" }
|
| 522 |
+
dependencies = [
|
| 523 |
+
{ name = "smmap" },
|
| 524 |
+
]
|
| 525 |
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload_time = "2025-01-02T07:20:46.413Z" }
|
| 526 |
+
wheels = [
|
| 527 |
+
{ url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload_time = "2025-01-02T07:20:43.624Z" },
|
| 528 |
+
]
|
| 529 |
+
|
| 530 |
+
[[package]]
|
| 531 |
+
name = "gitpython"
|
| 532 |
+
version = "3.1.45"
|
| 533 |
+
source = { registry = "https://pypi.org/simple" }
|
| 534 |
+
dependencies = [
|
| 535 |
+
{ name = "gitdb" },
|
| 536 |
+
]
|
| 537 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload_time = "2025-07-24T03:45:54.871Z" }
|
| 538 |
+
wheels = [
|
| 539 |
+
{ url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload_time = "2025-07-24T03:45:52.517Z" },
|
| 540 |
+
]
|
| 541 |
+
|
| 542 |
[[package]]
|
| 543 |
name = "gnews"
|
| 544 |
version = "0.4.1"
|
|
|
|
| 885 |
source = { registry = "https://pypi.org/simple" }
|
| 886 |
sdist = { url = "https://files.pythonhosted.org/packages/17/0d/74f0293dfd7dcc3837746d0138cbedd60b31701ecc75caec7d3f281feba0/multitasking-0.0.12.tar.gz", hash = "sha256:2fba2fa8ed8c4b85e227c5dd7dc41c7d658de3b6f247927316175a57349b84d1", size = 19984, upload_time = "2025-07-20T21:27:51.636Z" }
|
| 887 |
|
| 888 |
+
[[package]]
|
| 889 |
+
name = "narwhals"
|
| 890 |
+
version = "1.48.1"
|
| 891 |
+
source = { registry = "https://pypi.org/simple" }
|
| 892 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9b/da/fe15ccd311ebb8fbbdacc447ba5888306c0b4a6253f628d60df351c36c7d/narwhals-1.48.1.tar.gz", hash = "sha256:b375cfdfc20b84b5ac0926f34c5c1373eb23ebea48d47bf75e282161cda63e34", size = 515882, upload_time = "2025-07-24T19:02:19.14Z" }
|
| 893 |
+
wheels = [
|
| 894 |
+
{ url = "https://files.pythonhosted.org/packages/cd/cf/411b2083991c6906634910ea0c5e5ea0a01f7f14da4194b39d7ad054c187/narwhals-1.48.1-py3-none-any.whl", hash = "sha256:76e3b069cf20a2746d8e227686b959530e98e8018c594a04e5f4f6f77e0872d9", size = 377332, upload_time = "2025-07-24T19:02:17.548Z" },
|
| 895 |
+
]
|
| 896 |
+
|
| 897 |
[[package]]
|
| 898 |
name = "networkx"
|
| 899 |
version = "3.4.2"
|
|
|
|
| 1408 |
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload_time = "2025-05-07T22:47:40.376Z" },
|
| 1409 |
]
|
| 1410 |
|
| 1411 |
+
[[package]]
|
| 1412 |
+
name = "plotly"
|
| 1413 |
+
version = "6.2.0"
|
| 1414 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1415 |
+
dependencies = [
|
| 1416 |
+
{ name = "narwhals" },
|
| 1417 |
+
{ name = "packaging" },
|
| 1418 |
+
]
|
| 1419 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6e/5c/0efc297df362b88b74957a230af61cd6929f531f72f48063e8408702ffba/plotly-6.2.0.tar.gz", hash = "sha256:9dfa23c328000f16c928beb68927444c1ab9eae837d1fe648dbcda5360c7953d", size = 6801941, upload_time = "2025-06-26T16:20:45.765Z" }
|
| 1420 |
+
wheels = [
|
| 1421 |
+
{ url = "https://files.pythonhosted.org/packages/ed/20/f2b7ac96a91cc5f70d81320adad24cc41bf52013508d649b1481db225780/plotly-6.2.0-py3-none-any.whl", hash = "sha256:32c444d4c940887219cb80738317040363deefdfee4f354498cc0b6dab8978bd", size = 9635469, upload_time = "2025-06-26T16:20:40.76Z" },
|
| 1422 |
+
]
|
| 1423 |
+
|
| 1424 |
[[package]]
|
| 1425 |
name = "protobuf"
|
| 1426 |
version = "6.31.1"
|
|
|
|
| 1450 |
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload_time = "2025-02-13T21:54:37.486Z" },
|
| 1451 |
]
|
| 1452 |
|
| 1453 |
+
[[package]]
|
| 1454 |
+
name = "pyarrow"
|
| 1455 |
+
version = "21.0.0"
|
| 1456 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1457 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload_time = "2025-07-18T00:57:31.761Z" }
|
| 1458 |
+
wheels = [
|
| 1459 |
+
{ url = "https://files.pythonhosted.org/packages/17/d9/110de31880016e2afc52d8580b397dbe47615defbf09ca8cf55f56c62165/pyarrow-21.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e563271e2c5ff4d4a4cbeb2c83d5cf0d4938b891518e676025f7268c6fe5fe26", size = 31196837, upload_time = "2025-07-18T00:54:34.755Z" },
|
| 1460 |
+
{ url = "https://files.pythonhosted.org/packages/df/5f/c1c1997613abf24fceb087e79432d24c19bc6f7259cab57c2c8e5e545fab/pyarrow-21.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fee33b0ca46f4c85443d6c450357101e47d53e6c3f008d658c27a2d020d44c79", size = 32659470, upload_time = "2025-07-18T00:54:38.329Z" },
|
| 1461 |
+
{ url = "https://files.pythonhosted.org/packages/3e/ed/b1589a777816ee33ba123ba1e4f8f02243a844fed0deec97bde9fb21a5cf/pyarrow-21.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7be45519b830f7c24b21d630a31d48bcebfd5d4d7f9d3bdb49da9cdf6d764edb", size = 41055619, upload_time = "2025-07-18T00:54:42.172Z" },
|
| 1462 |
+
{ url = "https://files.pythonhosted.org/packages/44/28/b6672962639e85dc0ac36f71ab3a8f5f38e01b51343d7aa372a6b56fa3f3/pyarrow-21.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:26bfd95f6bff443ceae63c65dc7e048670b7e98bc892210acba7e4995d3d4b51", size = 42733488, upload_time = "2025-07-18T00:54:47.132Z" },
|
| 1463 |
+
{ url = "https://files.pythonhosted.org/packages/f8/cc/de02c3614874b9089c94eac093f90ca5dfa6d5afe45de3ba847fd950fdf1/pyarrow-21.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd04ec08f7f8bd113c55868bd3fc442a9db67c27af098c5f814a3091e71cc61a", size = 43329159, upload_time = "2025-07-18T00:54:51.686Z" },
|
| 1464 |
+
{ url = "https://files.pythonhosted.org/packages/a6/3e/99473332ac40278f196e105ce30b79ab8affab12f6194802f2593d6b0be2/pyarrow-21.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9b0b14b49ac10654332a805aedfc0147fb3469cbf8ea951b3d040dab12372594", size = 45050567, upload_time = "2025-07-18T00:54:56.679Z" },
|
| 1465 |
+
{ url = "https://files.pythonhosted.org/packages/7b/f5/c372ef60593d713e8bfbb7e0c743501605f0ad00719146dc075faf11172b/pyarrow-21.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9d9f8bcb4c3be7738add259738abdeddc363de1b80e3310e04067aa1ca596634", size = 26217959, upload_time = "2025-07-18T00:55:00.482Z" },
|
| 1466 |
+
{ url = "https://files.pythonhosted.org/packages/94/dc/80564a3071a57c20b7c32575e4a0120e8a330ef487c319b122942d665960/pyarrow-21.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c077f48aab61738c237802836fc3844f85409a46015635198761b0d6a688f87b", size = 31243234, upload_time = "2025-07-18T00:55:03.812Z" },
|
| 1467 |
+
{ url = "https://files.pythonhosted.org/packages/ea/cc/3b51cb2db26fe535d14f74cab4c79b191ed9a8cd4cbba45e2379b5ca2746/pyarrow-21.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:689f448066781856237eca8d1975b98cace19b8dd2ab6145bf49475478bcaa10", size = 32714370, upload_time = "2025-07-18T00:55:07.495Z" },
|
| 1468 |
+
{ url = "https://files.pythonhosted.org/packages/24/11/a4431f36d5ad7d83b87146f515c063e4d07ef0b7240876ddb885e6b44f2e/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:479ee41399fcddc46159a551705b89c05f11e8b8cb8e968f7fec64f62d91985e", size = 41135424, upload_time = "2025-07-18T00:55:11.461Z" },
|
| 1469 |
+
{ url = "https://files.pythonhosted.org/packages/74/dc/035d54638fc5d2971cbf1e987ccd45f1091c83bcf747281cf6cc25e72c88/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:40ebfcb54a4f11bcde86bc586cbd0272bac0d516cfa539c799c2453768477569", size = 42823810, upload_time = "2025-07-18T00:55:16.301Z" },
|
| 1470 |
+
{ url = "https://files.pythonhosted.org/packages/2e/3b/89fced102448a9e3e0d4dded1f37fa3ce4700f02cdb8665457fcc8015f5b/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8d58d8497814274d3d20214fbb24abcad2f7e351474357d552a8d53bce70c70e", size = 43391538, upload_time = "2025-07-18T00:55:23.82Z" },
|
| 1471 |
+
{ url = "https://files.pythonhosted.org/packages/fb/bb/ea7f1bd08978d39debd3b23611c293f64a642557e8141c80635d501e6d53/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:585e7224f21124dd57836b1530ac8f2df2afc43c861d7bf3d58a4870c42ae36c", size = 45120056, upload_time = "2025-07-18T00:55:28.231Z" },
|
| 1472 |
+
{ url = "https://files.pythonhosted.org/packages/6e/0b/77ea0600009842b30ceebc3337639a7380cd946061b620ac1a2f3cb541e2/pyarrow-21.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:555ca6935b2cbca2c0e932bedd853e9bc523098c39636de9ad4693b5b1df86d6", size = 26220568, upload_time = "2025-07-18T00:55:32.122Z" },
|
| 1473 |
+
{ url = "https://files.pythonhosted.org/packages/ca/d4/d4f817b21aacc30195cf6a46ba041dd1be827efa4a623cc8bf39a1c2a0c0/pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd", size = 31160305, upload_time = "2025-07-18T00:55:35.373Z" },
|
| 1474 |
+
{ url = "https://files.pythonhosted.org/packages/a2/9c/dcd38ce6e4b4d9a19e1d36914cb8e2b1da4e6003dd075474c4cfcdfe0601/pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876", size = 32684264, upload_time = "2025-07-18T00:55:39.303Z" },
|
| 1475 |
+
{ url = "https://files.pythonhosted.org/packages/4f/74/2a2d9f8d7a59b639523454bec12dba35ae3d0a07d8ab529dc0809f74b23c/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d", size = 41108099, upload_time = "2025-07-18T00:55:42.889Z" },
|
| 1476 |
+
{ url = "https://files.pythonhosted.org/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e", size = 42829529, upload_time = "2025-07-18T00:55:47.069Z" },
|
| 1477 |
+
{ url = "https://files.pythonhosted.org/packages/33/27/1a93a25c92717f6aa0fca06eb4700860577d016cd3ae51aad0e0488ac899/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82", size = 43367883, upload_time = "2025-07-18T00:55:53.069Z" },
|
| 1478 |
+
{ url = "https://files.pythonhosted.org/packages/05/d9/4d09d919f35d599bc05c6950095e358c3e15148ead26292dfca1fb659b0c/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623", size = 45133802, upload_time = "2025-07-18T00:55:57.714Z" },
|
| 1479 |
+
{ url = "https://files.pythonhosted.org/packages/71/30/f3795b6e192c3ab881325ffe172e526499eb3780e306a15103a2764916a2/pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18", size = 26203175, upload_time = "2025-07-18T00:56:01.364Z" },
|
| 1480 |
+
{ url = "https://files.pythonhosted.org/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload_time = "2025-07-18T00:56:04.42Z" },
|
| 1481 |
+
{ url = "https://files.pythonhosted.org/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload_time = "2025-07-18T00:56:07.505Z" },
|
| 1482 |
+
{ url = "https://files.pythonhosted.org/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload_time = "2025-07-18T00:56:10.994Z" },
|
| 1483 |
+
{ url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload_time = "2025-07-18T00:56:15.569Z" },
|
| 1484 |
+
{ url = "https://files.pythonhosted.org/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload_time = "2025-07-18T00:56:19.531Z" },
|
| 1485 |
+
{ url = "https://files.pythonhosted.org/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload_time = "2025-07-18T00:56:23.347Z" },
|
| 1486 |
+
{ url = "https://files.pythonhosted.org/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload_time = "2025-07-18T00:56:26.758Z" },
|
| 1487 |
+
{ url = "https://files.pythonhosted.org/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload_time = "2025-07-18T00:56:30.214Z" },
|
| 1488 |
+
{ url = "https://files.pythonhosted.org/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload_time = "2025-07-18T00:56:33.935Z" },
|
| 1489 |
+
{ url = "https://files.pythonhosted.org/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload_time = "2025-07-18T00:56:37.528Z" },
|
| 1490 |
+
{ url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload_time = "2025-07-18T00:56:41.483Z" },
|
| 1491 |
+
{ url = "https://files.pythonhosted.org/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload_time = "2025-07-18T00:56:48.002Z" },
|
| 1492 |
+
{ url = "https://files.pythonhosted.org/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload_time = "2025-07-18T00:56:52.568Z" },
|
| 1493 |
+
{ url = "https://files.pythonhosted.org/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload_time = "2025-07-18T00:56:56.379Z" },
|
| 1494 |
+
]
|
| 1495 |
+
|
| 1496 |
[[package]]
|
| 1497 |
name = "pycparser"
|
| 1498 |
version = "2.22"
|
|
|
|
| 1623 |
{ url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload_time = "2025-06-24T13:26:45.485Z" },
|
| 1624 |
]
|
| 1625 |
|
| 1626 |
+
[[package]]
|
| 1627 |
+
name = "pydeck"
|
| 1628 |
+
version = "0.9.1"
|
| 1629 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1630 |
+
dependencies = [
|
| 1631 |
+
{ name = "jinja2" },
|
| 1632 |
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
| 1633 |
+
{ name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
| 1634 |
+
]
|
| 1635 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/40e14e196864a0f61a92abb14d09b3d3da98f94ccb03b49cf51688140dab/pydeck-0.9.1.tar.gz", hash = "sha256:f74475ae637951d63f2ee58326757f8d4f9cd9f2a457cf42950715003e2cb605", size = 3832240, upload_time = "2024-05-10T15:36:21.153Z" }
|
| 1636 |
+
wheels = [
|
| 1637 |
+
{ url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403, upload_time = "2024-05-10T15:36:17.36Z" },
|
| 1638 |
+
]
|
| 1639 |
+
|
| 1640 |
[[package]]
|
| 1641 |
name = "pygments"
|
| 1642 |
version = "2.19.2"
|
|
|
|
| 2233 |
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload_time = "2024-12-04T17:35:26.475Z" },
|
| 2234 |
]
|
| 2235 |
|
| 2236 |
+
[[package]]
|
| 2237 |
+
name = "smmap"
|
| 2238 |
+
version = "5.0.2"
|
| 2239 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2240 |
+
sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload_time = "2025-01-02T07:14:40.909Z" }
|
| 2241 |
+
wheels = [
|
| 2242 |
+
{ url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload_time = "2025-01-02T07:14:38.724Z" },
|
| 2243 |
+
]
|
| 2244 |
+
|
| 2245 |
[[package]]
|
| 2246 |
name = "sniffio"
|
| 2247 |
version = "1.3.1"
|
|
|
|
| 2285 |
{ url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload_time = "2025-07-20T17:31:56.738Z" },
|
| 2286 |
]
|
| 2287 |
|
| 2288 |
+
[[package]]
|
| 2289 |
+
name = "streamlit"
|
| 2290 |
+
version = "1.47.1"
|
| 2291 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2292 |
+
dependencies = [
|
| 2293 |
+
{ name = "altair" },
|
| 2294 |
+
{ name = "blinker" },
|
| 2295 |
+
{ name = "cachetools" },
|
| 2296 |
+
{ name = "click" },
|
| 2297 |
+
{ name = "gitpython" },
|
| 2298 |
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
| 2299 |
+
{ name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
| 2300 |
+
{ name = "packaging" },
|
| 2301 |
+
{ name = "pandas" },
|
| 2302 |
+
{ name = "pillow" },
|
| 2303 |
+
{ name = "protobuf" },
|
| 2304 |
+
{ name = "pyarrow" },
|
| 2305 |
+
{ name = "pydeck" },
|
| 2306 |
+
{ name = "requests" },
|
| 2307 |
+
{ name = "tenacity" },
|
| 2308 |
+
{ name = "toml" },
|
| 2309 |
+
{ name = "tornado" },
|
| 2310 |
+
{ name = "typing-extensions" },
|
| 2311 |
+
{ name = "watchdog", marker = "sys_platform != 'darwin'" },
|
| 2312 |
+
]
|
| 2313 |
+
sdist = { url = "https://files.pythonhosted.org/packages/19/da/cef67ed4614f04932a00068fe6291455deb884a04fd94f7ad78492b0e91a/streamlit-1.47.1.tar.gz", hash = "sha256:daed79763d1cafeb03cdd800b91aa9c7adc3688c6b2cbf4ecc2ca899aab82a2a", size = 9544057, upload_time = "2025-07-25T15:37:08.482Z" }
|
| 2314 |
+
wheels = [
|
| 2315 |
+
{ url = "https://files.pythonhosted.org/packages/c0/4d/701f5fcf9c0d388dad9d94ba272d333c7efa6231ddee1babc59d26dc14d2/streamlit-1.47.1-py3-none-any.whl", hash = "sha256:c7881549e3ba1daecfb5541f32ee6ff70e549f1c3400c92d045897cb7a29772a", size = 9944872, upload_time = "2025-07-25T15:37:05.758Z" },
|
| 2316 |
+
]
|
| 2317 |
+
|
| 2318 |
[[package]]
|
| 2319 |
name = "sympy"
|
| 2320 |
version = "1.14.0"
|
|
|
|
| 2327 |
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload_time = "2025-04-27T18:04:59.103Z" },
|
| 2328 |
]
|
| 2329 |
|
| 2330 |
+
[[package]]
|
| 2331 |
+
name = "tenacity"
|
| 2332 |
+
version = "9.1.2"
|
| 2333 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2334 |
+
sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload_time = "2025-04-02T08:25:09.966Z" }
|
| 2335 |
+
wheels = [
|
| 2336 |
+
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload_time = "2025-04-02T08:25:07.678Z" },
|
| 2337 |
+
]
|
| 2338 |
+
|
| 2339 |
[[package]]
|
| 2340 |
name = "threadpoolctl"
|
| 2341 |
version = "3.6.0"
|
|
|
|
| 2370 |
{ url = "https://files.pythonhosted.org/packages/13/c3/cc2755ee10be859c4338c962a35b9a663788c0c0b50c0bdd8078fb6870cf/tokenizers-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:58747bb898acdb1007f37a7bbe614346e98dc28708ffb66a3fd50ce169ac6c98", size = 2509918, upload_time = "2025-06-24T10:24:53.71Z" },
|
| 2371 |
]
|
| 2372 |
|
| 2373 |
+
[[package]]
|
| 2374 |
+
name = "toml"
|
| 2375 |
+
version = "0.10.2"
|
| 2376 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2377 |
+
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload_time = "2020-11-01T01:40:22.204Z" }
|
| 2378 |
+
wheels = [
|
| 2379 |
+
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload_time = "2020-11-01T01:40:20.672Z" },
|
| 2380 |
+
]
|
| 2381 |
+
|
| 2382 |
[[package]]
|
| 2383 |
name = "torch"
|
| 2384 |
version = "2.7.1"
|
|
|
|
| 2431 |
{ url = "https://files.pythonhosted.org/packages/b1/29/beb45cdf5c4fc3ebe282bf5eafc8dfd925ead7299b3c97491900fe5ed844/torch-2.7.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:988b0cbc4333618a1056d2ebad9eb10089637b659eb645434d0809d8d937b946", size = 68645708, upload_time = "2025-06-04T17:34:39.852Z" },
|
| 2432 |
]
|
| 2433 |
|
| 2434 |
+
[[package]]
|
| 2435 |
+
name = "tornado"
|
| 2436 |
+
version = "6.5.1"
|
| 2437 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2438 |
+
sdist = { url = "https://files.pythonhosted.org/packages/51/89/c72771c81d25d53fe33e3dca61c233b665b2780f21820ba6fd2c6793c12b/tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c", size = 509934, upload_time = "2025-05-22T18:15:38.788Z" }
|
| 2439 |
+
wheels = [
|
| 2440 |
+
{ url = "https://files.pythonhosted.org/packages/77/89/f4532dee6843c9e0ebc4e28d4be04c67f54f60813e4bf73d595fe7567452/tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7", size = 441948, upload_time = "2025-05-22T18:15:20.862Z" },
|
| 2441 |
+
{ url = "https://files.pythonhosted.org/packages/15/9a/557406b62cffa395d18772e0cdcf03bed2fff03b374677348eef9f6a3792/tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6", size = 440112, upload_time = "2025-05-22T18:15:22.591Z" },
|
| 2442 |
+
{ url = "https://files.pythonhosted.org/packages/55/82/7721b7319013a3cf881f4dffa4f60ceff07b31b394e459984e7a36dc99ec/tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888", size = 443672, upload_time = "2025-05-22T18:15:24.027Z" },
|
| 2443 |
+
{ url = "https://files.pythonhosted.org/packages/7d/42/d11c4376e7d101171b94e03cef0cbce43e823ed6567ceda571f54cf6e3ce/tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331", size = 443019, upload_time = "2025-05-22T18:15:25.735Z" },
|
| 2444 |
+
{ url = "https://files.pythonhosted.org/packages/7d/f7/0c48ba992d875521ac761e6e04b0a1750f8150ae42ea26df1852d6a98942/tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e", size = 443252, upload_time = "2025-05-22T18:15:27.499Z" },
|
| 2445 |
+
{ url = "https://files.pythonhosted.org/packages/89/46/d8d7413d11987e316df4ad42e16023cd62666a3c0dfa1518ffa30b8df06c/tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401", size = 443930, upload_time = "2025-05-22T18:15:29.299Z" },
|
| 2446 |
+
{ url = "https://files.pythonhosted.org/packages/78/b2/f8049221c96a06df89bed68260e8ca94beca5ea532ffc63b1175ad31f9cc/tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692", size = 443351, upload_time = "2025-05-22T18:15:31.038Z" },
|
| 2447 |
+
{ url = "https://files.pythonhosted.org/packages/76/ff/6a0079e65b326cc222a54720a748e04a4db246870c4da54ece4577bfa702/tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a", size = 443328, upload_time = "2025-05-22T18:15:32.426Z" },
|
| 2448 |
+
{ url = "https://files.pythonhosted.org/packages/49/18/e3f902a1d21f14035b5bc6246a8c0f51e0eef562ace3a2cea403c1fb7021/tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365", size = 444396, upload_time = "2025-05-22T18:15:34.205Z" },
|
| 2449 |
+
{ url = "https://files.pythonhosted.org/packages/7b/09/6526e32bf1049ee7de3bebba81572673b19a2a8541f795d887e92af1a8bc/tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b", size = 444840, upload_time = "2025-05-22T18:15:36.1Z" },
|
| 2450 |
+
{ url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596, upload_time = "2025-05-22T18:15:37.433Z" },
|
| 2451 |
+
]
|
| 2452 |
+
|
| 2453 |
[[package]]
|
| 2454 |
name = "tqdm"
|
| 2455 |
version = "4.67.1"
|
|
|
|
| 2573 |
{ url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload_time = "2025-06-28T16:15:44.816Z" },
|
| 2574 |
]
|
| 2575 |
|
| 2576 |
+
[[package]]
|
| 2577 |
+
name = "watchdog"
|
| 2578 |
+
version = "6.0.0"
|
| 2579 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2580 |
+
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload_time = "2024-11-01T14:07:13.037Z" }
|
| 2581 |
+
wheels = [
|
| 2582 |
+
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload_time = "2024-11-01T14:06:59.472Z" },
|
| 2583 |
+
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload_time = "2024-11-01T14:07:01.431Z" },
|
| 2584 |
+
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload_time = "2024-11-01T14:07:02.568Z" },
|
| 2585 |
+
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload_time = "2024-11-01T14:07:03.893Z" },
|
| 2586 |
+
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload_time = "2024-11-01T14:07:05.189Z" },
|
| 2587 |
+
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload_time = "2024-11-01T14:07:06.376Z" },
|
| 2588 |
+
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload_time = "2024-11-01T14:07:07.547Z" },
|
| 2589 |
+
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload_time = "2024-11-01T14:07:09.525Z" },
|
| 2590 |
+
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload_time = "2024-11-01T14:07:10.686Z" },
|
| 2591 |
+
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload_time = "2024-11-01T14:07:11.845Z" },
|
| 2592 |
+
]
|
| 2593 |
+
|
| 2594 |
[[package]]
|
| 2595 |
name = "websockets"
|
| 2596 |
version = "15.0.1"
|