Spaces:
Running
Running
Update app/app.py
Browse files- app/app.py +22 -12
app/app.py
CHANGED
@@ -18,9 +18,10 @@ from fastapi import FastAPI, Request
|
|
18 |
from fastapi.responses import HTMLResponse, StreamingResponse
|
19 |
from fastapi.templating import Jinja2Templates
|
20 |
|
21 |
-
# Use relative imports
|
22 |
-
|
23 |
-
from
|
|
|
24 |
from newsapi import NewsApiClient
|
25 |
|
26 |
|
@@ -38,15 +39,17 @@ async def lifespan(app: FastAPI):
|
|
38 |
app.state.gemini_analyzer = GeminiAnalyzer(client=client)
|
39 |
app.state.news_api = NewsApiClient(api_key=os.getenv("NEWS_API_KEY"))
|
40 |
|
41 |
-
#
|
|
|
42 |
app.state.signal_queue: asyncio.Queue = asyncio.Queue()
|
|
|
43 |
|
44 |
# Create cancellable background tasks for periodic updates.
|
45 |
price_task = asyncio.create_task(
|
46 |
run_periodic_updates(app.state.price_fetcher, interval_seconds=60)
|
47 |
)
|
48 |
news_task = asyncio.create_task(
|
49 |
-
run_periodic_news_analysis(app, interval_seconds=900) #
|
50 |
)
|
51 |
|
52 |
print("π Sentinel TradeFlow Protocol started successfully.")
|
@@ -83,8 +86,18 @@ async def run_periodic_news_analysis(app: FastAPI, interval_seconds: int):
|
|
83 |
title = article.get('title')
|
84 |
if title and "[Removed]" not in title:
|
85 |
analysis = await analyzer.analyze_text(title)
|
|
|
|
|
|
|
|
|
|
|
86 |
analysis['url'] = article.get('url')
|
|
|
|
|
87 |
await app.state.signal_queue.put(analysis)
|
|
|
|
|
|
|
88 |
except Exception as e:
|
89 |
print(f"β Error during news fetching or analysis: {e}")
|
90 |
|
@@ -131,19 +144,16 @@ async def serve_dashboard(request: Request):
|
|
131 |
@app.get("/api/signals/stream")
|
132 |
async def signal_stream(request: Request):
|
133 |
"""SSE stream for the automated Signal Stream."""
|
134 |
-
#
|
135 |
-
# FIX APPLIED HERE
|
136 |
-
# ====================================================================
|
137 |
-
# Access the shared application state via `request.app.state`, not `request.state`.
|
138 |
queue: asyncio.Queue = request.app.state.signal_queue
|
139 |
-
# ====================================================================
|
140 |
-
|
141 |
async def event_generator():
|
|
|
|
|
142 |
while True:
|
143 |
payload = await queue.get()
|
144 |
html = render_signal_card(payload)
|
145 |
data_payload = html.replace('\n', '')
|
146 |
-
# Use a generic 'message' event, which HTMX listens to by default
|
147 |
sse_message = f"event: message\ndata: {data_payload}\n\n"
|
148 |
yield sse_message
|
|
|
149 |
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
|
|
18 |
from fastapi.responses import HTMLResponse, StreamingResponse
|
19 |
from fastapi.templating import Jinja2Templates
|
20 |
|
21 |
+
# Use relative imports if your files are in an 'app' package,
|
22 |
+
# or direct imports if they are at the root. Assuming root for simplicity now.
|
23 |
+
from price_fetcher import PriceFetcher
|
24 |
+
from gemini_analyzer import GeminiAnalyzer
|
25 |
from newsapi import NewsApiClient
|
26 |
|
27 |
|
|
|
39 |
app.state.gemini_analyzer = GeminiAnalyzer(client=client)
|
40 |
app.state.news_api = NewsApiClient(api_key=os.getenv("NEWS_API_KEY"))
|
41 |
|
42 |
+
# =================== FIX APPLIED HERE (1/2) ===================
|
43 |
+
# We only need ONE queue for the autonomous signal stream.
|
44 |
app.state.signal_queue: asyncio.Queue = asyncio.Queue()
|
45 |
+
# =============================================================
|
46 |
|
47 |
# Create cancellable background tasks for periodic updates.
|
48 |
price_task = asyncio.create_task(
|
49 |
run_periodic_updates(app.state.price_fetcher, interval_seconds=60)
|
50 |
)
|
51 |
news_task = asyncio.create_task(
|
52 |
+
run_periodic_news_analysis(app, interval_seconds=900) # Check news every 15 minutes
|
53 |
)
|
54 |
|
55 |
print("π Sentinel TradeFlow Protocol started successfully.")
|
|
|
86 |
title = article.get('title')
|
87 |
if title and "[Removed]" not in title:
|
88 |
analysis = await analyzer.analyze_text(title)
|
89 |
+
# Don't proceed if Gemini returned an error
|
90 |
+
if analysis.get("error"):
|
91 |
+
print(f"Skipping article due to Gemini error: {analysis['reason']}")
|
92 |
+
continue
|
93 |
+
|
94 |
analysis['url'] = article.get('url')
|
95 |
+
# =================== FIX APPLIED HERE (2/2) ===================
|
96 |
+
# Ensure the result is put into the one and only signal_queue.
|
97 |
await app.state.signal_queue.put(analysis)
|
98 |
+
print(f"β
Signal generated and queued for: {title}")
|
99 |
+
# =============================================================
|
100 |
+
|
101 |
except Exception as e:
|
102 |
print(f"β Error during news fetching or analysis: {e}")
|
103 |
|
|
|
144 |
@app.get("/api/signals/stream")
|
145 |
async def signal_stream(request: Request):
|
146 |
"""SSE stream for the automated Signal Stream."""
|
147 |
+
# This was already correct from the last fix, but we confirm it points to signal_queue.
|
|
|
|
|
|
|
148 |
queue: asyncio.Queue = request.app.state.signal_queue
|
|
|
|
|
149 |
async def event_generator():
|
150 |
+
# Let's send a confirmation that the stream is connected and ready
|
151 |
+
yield f"event: message\ndata: <div hx-swap-oob='innerHTML' id='signal-stream-container'><p>Status: ONLINE - Listening for new market signals...</p></div>\n\n"
|
152 |
while True:
|
153 |
payload = await queue.get()
|
154 |
html = render_signal_card(payload)
|
155 |
data_payload = html.replace('\n', '')
|
|
|
156 |
sse_message = f"event: message\ndata: {data_payload}\n\n"
|
157 |
yield sse_message
|
158 |
+
|
159 |
return StreamingResponse(event_generator(), media_type="text/event-stream")
|