Commit
·
6ffbf76
1
Parent(s):
683938d
more efficiewnt backend
Browse files- Dockerfile +0 -9
- backend/Dockerfile +0 -40
- backend/celery_worker.py +0 -1
- backend/core/config.py +2 -17
- backend/main.py +0 -72
- backend/models/analysis_job.py +0 -15
- backend/requirements.txt +1 -9
- backend/tasks/analyst_tasks.py +0 -107
- backend/tasks/data_tasks.py +0 -90
- backend/tasks/main_task.py +1 -113
- backend/tasks/news_tasks.py +0 -341
- backend/tmp.py +0 -2
- backend/tools/prediction_tools.py +0 -4
- docker-compose.yml +0 -51
- frontend/src/App.jsx +0 -122
- frontend/src/components/Header.jsx +1 -1
- frontend/src/components/HistoricalChart.jsx +0 -2
- frontend/src/components/HistoryPanel.jsx +0 -3
- frontend/src/components/JobForm.jsx +1 -2
- frontend/src/components/JobStatusCard.jsx +0 -1
- frontend/src/components/LoadingSkeleton.jsx +0 -1
- frontend/src/services/api.js +0 -25
- tmp_down.py +0 -1
Dockerfile
CHANGED
@@ -1,28 +1,19 @@
|
|
1 |
-
# This is the single Dockerfile for our entire backend on Hugging Face Spaces
|
2 |
FROM python:3.11-slim
|
3 |
|
4 |
-
# Set a single working directory
|
5 |
WORKDIR /app
|
6 |
|
7 |
-
# Install system dependencies
|
8 |
RUN apt-get update && apt-get install -y git redis-server
|
9 |
|
10 |
-
# Copy all requirements and model files first for better caching
|
11 |
COPY backend/requirements.txt .
|
12 |
COPY ml_models ./ml_models
|
13 |
|
14 |
-
# Install Python packages
|
15 |
RUN pip install --no-cache-dir -r requirements.txt
|
16 |
|
17 |
-
# Copy the entire backend source code
|
18 |
COPY backend .
|
19 |
|
20 |
-
# Create a startup script
|
21 |
COPY startup.sh .
|
22 |
RUN chmod +x startup.sh
|
23 |
|
24 |
-
# Expose the port FastAPI will run on
|
25 |
EXPOSE 7860
|
26 |
|
27 |
-
# The command to run our startup script
|
28 |
CMD ["./startup.sh"]
|
|
|
|
|
1 |
FROM python:3.11-slim
|
2 |
|
|
|
3 |
WORKDIR /app
|
4 |
|
|
|
5 |
RUN apt-get update && apt-get install -y git redis-server
|
6 |
|
|
|
7 |
COPY backend/requirements.txt .
|
8 |
COPY ml_models ./ml_models
|
9 |
|
|
|
10 |
RUN pip install --no-cache-dir -r requirements.txt
|
11 |
|
|
|
12 |
COPY backend .
|
13 |
|
|
|
14 |
COPY startup.sh .
|
15 |
RUN chmod +x startup.sh
|
16 |
|
|
|
17 |
EXPOSE 7860
|
18 |
|
|
|
19 |
CMD ["./startup.sh"]
|
backend/Dockerfile
CHANGED
@@ -14,43 +14,3 @@ WORKDIR /code/app
|
|
14 |
|
15 |
|
16 |
|
17 |
-
# FROM python:3.11-slim
|
18 |
-
|
19 |
-
# WORKDIR /code
|
20 |
-
|
21 |
-
# RUN apt-get update && apt-get install -y git
|
22 |
-
|
23 |
-
# COPY ./backend/requirements.txt .
|
24 |
-
# RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
25 |
-
|
26 |
-
# COPY ./ml_models /code/sentiment_model
|
27 |
-
|
28 |
-
# WORKDIR /code/app
|
29 |
-
|
30 |
-
# # This is the default command for our web server
|
31 |
-
# CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "10000"]
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
# FROM python:3.11-slim
|
39 |
-
|
40 |
-
# WORKDIR /code
|
41 |
-
|
42 |
-
# RUN apt-get update && apt-get install -y git
|
43 |
-
|
44 |
-
# COPY ./backend/requirements.txt .
|
45 |
-
# # Install Gunicorn for a production-ready server
|
46 |
-
# RUN pip install gunicorn
|
47 |
-
# RUN pip install --no-cache-dir -r requirements.txt
|
48 |
-
|
49 |
-
# COPY ./ml_models /code/sentiment_model
|
50 |
-
|
51 |
-
# # Copy the application code last. All code will live in /code now.
|
52 |
-
# COPY ./backend .
|
53 |
-
|
54 |
-
# # The default command is to start the web server.
|
55 |
-
# # Render's free web services require port 10000.
|
56 |
-
# CMD ["gunicorn", "-w", "2", "-k", "uvicorn.workers.UvicornWorker", "main:app", "--bind", "0.0.0.0:10000"]
|
|
|
14 |
|
15 |
|
16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/celery_worker.py
CHANGED
@@ -5,7 +5,6 @@ celery = Celery(
|
|
5 |
"quantitative_analysis_platform",
|
6 |
broker=settings.CELERY_BROKER_URL,
|
7 |
backend=settings.CELERY_RESULT_BACKEND,
|
8 |
-
# This is the corrected list. We only have one task file now.
|
9 |
include=[
|
10 |
"tasks.main_task"
|
11 |
]
|
|
|
5 |
"quantitative_analysis_platform",
|
6 |
broker=settings.CELERY_BROKER_URL,
|
7 |
backend=settings.CELERY_RESULT_BACKEND,
|
|
|
8 |
include=[
|
9 |
"tasks.main_task"
|
10 |
]
|
backend/core/config.py
CHANGED
@@ -1,31 +1,16 @@
|
|
1 |
-
# from pydantic_settings import BaseSettings, SettingsConfigDict
|
2 |
-
|
3 |
-
# class Settings(BaseSettings):
|
4 |
-
# DATABASE_URL: str
|
5 |
-
# CELERY_BROKER_URL: str
|
6 |
-
# CELERY_RESULT_BACKEND: str
|
7 |
-
# GOOGLE_API_KEY: str
|
8 |
-
|
9 |
-
# model_config = SettingsConfigDict(env_file=".env")
|
10 |
-
|
11 |
-
# settings = Settings()
|
12 |
|
13 |
|
14 |
|
15 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
16 |
|
17 |
class Settings(BaseSettings):
|
18 |
-
# These variables will now be loaded from the Hugging Face secrets UI
|
19 |
DATABASE_URL: str
|
20 |
GOOGLE_API_KEY: str
|
21 |
-
|
22 |
-
# These variables are hardcoded for the Hugging Face environment
|
23 |
-
# because Redis is running in the same container.
|
24 |
CELERY_BROKER_URL: str = "redis://localhost:6379/0"
|
25 |
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/0"
|
26 |
|
27 |
-
|
28 |
-
# and then fall back to a .env file if one exists.
|
29 |
model_config = SettingsConfigDict(env_file=".env", env_file_encoding='utf-8', extra='ignore')
|
30 |
|
31 |
settings = Settings()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
|
2 |
|
3 |
|
4 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
5 |
|
6 |
class Settings(BaseSettings):
|
|
|
7 |
DATABASE_URL: str
|
8 |
GOOGLE_API_KEY: str
|
9 |
+
|
|
|
|
|
10 |
CELERY_BROKER_URL: str = "redis://localhost:6379/0"
|
11 |
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/0"
|
12 |
|
13 |
+
|
|
|
14 |
model_config = SettingsConfigDict(env_file=".env", env_file_encoding='utf-8', extra='ignore')
|
15 |
|
16 |
settings = Settings()
|
backend/main.py
CHANGED
@@ -1,71 +1,3 @@
|
|
1 |
-
# from fastapi import FastAPI, Depends, HTTPException
|
2 |
-
# from fastapi.middleware.cors import CORSMiddleware
|
3 |
-
# from sqlalchemy.orm import Session
|
4 |
-
# from sqlalchemy import desc # Import desc for ordering
|
5 |
-
# from uuid import UUID
|
6 |
-
# from typing import List # Import List for the history endpoint
|
7 |
-
# import models.analysis_job as model
|
8 |
-
# import schemas
|
9 |
-
# from core.database import SessionLocal, engine
|
10 |
-
# from tasks.main_task import run_full_analysis
|
11 |
-
|
12 |
-
# model.Base.metadata.create_all(bind=engine)
|
13 |
-
|
14 |
-
# app = FastAPI(
|
15 |
-
# title="Quantitative Analysis Platform API",
|
16 |
-
# version="0.1.0",
|
17 |
-
# )
|
18 |
-
|
19 |
-
# app.add_middleware(
|
20 |
-
# CORSMiddleware,
|
21 |
-
# allow_origins=["*"],
|
22 |
-
# allow_credentials=True,
|
23 |
-
# allow_methods=["*"],
|
24 |
-
# allow_headers=["*"],
|
25 |
-
# )
|
26 |
-
|
27 |
-
# def get_db():
|
28 |
-
# db = SessionLocal()
|
29 |
-
# try:
|
30 |
-
# yield db
|
31 |
-
# finally:
|
32 |
-
# db.close()
|
33 |
-
|
34 |
-
# @app.post("/jobs", response_model=schemas.Job, status_code=201)
|
35 |
-
# def create_analysis_job(job_request: schemas.JobCreate, db: Session = Depends(get_db)):
|
36 |
-
# db_job = model.AnalysisJob(ticker=job_request.ticker.upper())
|
37 |
-
# db.add(db_job)
|
38 |
-
# db.commit()
|
39 |
-
# db.refresh(db_job)
|
40 |
-
|
41 |
-
# run_full_analysis.delay(str(db_job.id), db_job.ticker)
|
42 |
-
|
43 |
-
# return db_job
|
44 |
-
|
45 |
-
# @app.get("/jobs/{job_id}", response_model=schemas.Job)
|
46 |
-
# def get_job_status(job_id: UUID, db: Session = Depends(get_db)):
|
47 |
-
# db_job = db.query(model.AnalysisJob).filter(model.AnalysisJob.id == job_id).first()
|
48 |
-
# if db_job is None:
|
49 |
-
# raise HTTPException(status_code=404, detail="Job not found")
|
50 |
-
# return db_job
|
51 |
-
|
52 |
-
# # --- NEW ENDPOINT FOR HISTORY PANEL ---
|
53 |
-
# @app.get("/jobs", response_model=List[schemas.Job])
|
54 |
-
# def get_jobs_history(db: Session = Depends(get_db)):
|
55 |
-
# db_jobs = db.query(model.AnalysisJob).order_by(desc(model.AnalysisJob.created_at)).limit(20).all()
|
56 |
-
# return db_jobs
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
from fastapi import FastAPI, Depends, HTTPException
|
70 |
from fastapi.middleware.cors import CORSMiddleware
|
71 |
from sqlalchemy.orm import Session
|
@@ -84,9 +16,6 @@ app = FastAPI(
|
|
84 |
version="0.1.0",
|
85 |
)
|
86 |
|
87 |
-
# --- THIS IS THE FINAL FIX ---
|
88 |
-
# This configuration allows your Vercel app and all its preview deployments
|
89 |
-
# to communicate with the backend, as well as your local development server.
|
90 |
app.add_middleware(
|
91 |
CORSMiddleware,
|
92 |
allow_origin_regex=r"https?://.*\.vercel\.app|http://localhost:5173",
|
@@ -94,7 +23,6 @@ app.add_middleware(
|
|
94 |
allow_methods=["*"],
|
95 |
allow_headers=["*"],
|
96 |
)
|
97 |
-
# --- END OF FIX ---
|
98 |
|
99 |
def get_db():
|
100 |
db = SessionLocal()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from fastapi import FastAPI, Depends, HTTPException
|
2 |
from fastapi.middleware.cors import CORSMiddleware
|
3 |
from sqlalchemy.orm import Session
|
|
|
16 |
version="0.1.0",
|
17 |
)
|
18 |
|
|
|
|
|
|
|
19 |
app.add_middleware(
|
20 |
CORSMiddleware,
|
21 |
allow_origin_regex=r"https?://.*\.vercel\.app|http://localhost:5173",
|
|
|
23 |
allow_methods=["*"],
|
24 |
allow_headers=["*"],
|
25 |
)
|
|
|
26 |
|
27 |
def get_db():
|
28 |
db = SessionLocal()
|
backend/models/analysis_job.py
CHANGED
@@ -1,18 +1,3 @@
|
|
1 |
-
# from sqlalchemy import Column, String, JSON
|
2 |
-
# from sqlalchemy.dialects.postgresql import UUID
|
3 |
-
# import uuid
|
4 |
-
# from core.database import Base
|
5 |
-
|
6 |
-
# class AnalysisJob(Base):
|
7 |
-
# __tablename__ = "analysis_jobs"
|
8 |
-
|
9 |
-
# id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
10 |
-
# ticker = Column(String, nullable=False, index=True)
|
11 |
-
# status = Column(String, default="PENDING", nullable=False)
|
12 |
-
# result = Column(JSON, nullable=True)
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
from sqlalchemy import Column, String, JSON, DateTime
|
17 |
from sqlalchemy.dialects.postgresql import UUID
|
18 |
import uuid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from sqlalchemy import Column, String, JSON, DateTime
|
2 |
from sqlalchemy.dialects.postgresql import UUID
|
3 |
import uuid
|
backend/requirements.txt
CHANGED
@@ -1,35 +1,27 @@
|
|
1 |
-
# FastAPI and Server
|
2 |
fastapi
|
3 |
uvicorn[standard]
|
4 |
gunicorn
|
5 |
pydantic-settings
|
6 |
|
7 |
-
# Database
|
8 |
sqlalchemy
|
9 |
psycopg2-binary
|
10 |
alembic
|
11 |
|
12 |
-
# Task Queue
|
13 |
celery
|
14 |
redis
|
15 |
|
16 |
-
|
17 |
-
numpy<2.0 # CRITICAL FIX: Pin numpy to a version compatible with pandas-ta
|
18 |
-
pandas
|
19 |
pandas-ta
|
20 |
matplotlib
|
21 |
|
22 |
-
# Data Agent & Prediction
|
23 |
yfinance
|
24 |
|
25 |
-
# Intelligence Agent
|
26 |
newspaper3k
|
27 |
lxml_html_clean
|
28 |
snscrape@git+https://github.com/JustAnotherArchivist/snscrape.git@master
|
29 |
requests
|
30 |
beautifulsoup4
|
31 |
|
32 |
-
# AI / ML / LLM
|
33 |
torch
|
34 |
transformers
|
35 |
sentence-transformers
|
|
|
|
|
1 |
fastapi
|
2 |
uvicorn[standard]
|
3 |
gunicorn
|
4 |
pydantic-settings
|
5 |
|
|
|
6 |
sqlalchemy
|
7 |
psycopg2-binary
|
8 |
alembic
|
9 |
|
|
|
10 |
celery
|
11 |
redis
|
12 |
|
13 |
+
numpy<2.0
|
|
|
|
|
14 |
pandas-ta
|
15 |
matplotlib
|
16 |
|
|
|
17 |
yfinance
|
18 |
|
|
|
19 |
newspaper3k
|
20 |
lxml_html_clean
|
21 |
snscrape@git+https://github.com/JustAnotherArchivist/snscrape.git@master
|
22 |
requests
|
23 |
beautifulsoup4
|
24 |
|
|
|
25 |
torch
|
26 |
transformers
|
27 |
sentence-transformers
|
backend/tasks/analyst_tasks.py
CHANGED
@@ -1,110 +1,3 @@
|
|
1 |
-
# from celery_worker import celery
|
2 |
-
# from core.database import SessionLocal
|
3 |
-
# from models.analysis_job import AnalysisJob
|
4 |
-
# from tools.analyst_tools import get_llm_analysis
|
5 |
-
# from uuid import UUID
|
6 |
-
|
7 |
-
# @celery.task
|
8 |
-
# def run_llm_analysis(job_id: str):
|
9 |
-
# db = SessionLocal()
|
10 |
-
# job = None
|
11 |
-
# try:
|
12 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
13 |
-
# if not job or not job.result:
|
14 |
-
# raise ValueError("Job not found or has no initial data.")
|
15 |
-
|
16 |
-
# job.status = "ANALYZING" # New status for the frontend
|
17 |
-
# db.commit()
|
18 |
-
|
19 |
-
# current_data = job.result
|
20 |
-
# ticker = current_data.get("ticker")
|
21 |
-
# company_name = current_data.get("company_name")
|
22 |
-
# intelligence_briefing = current_data.get("intelligence_briefing", {})
|
23 |
-
|
24 |
-
# llm_report_data = get_llm_analysis(ticker, company_name, intelligence_briefing)
|
25 |
-
|
26 |
-
# new_result = current_data.copy()
|
27 |
-
# new_result['llm_analysis'] = llm_report_data
|
28 |
-
# job.result = new_result
|
29 |
-
|
30 |
-
# job.status = "SUCCESS"
|
31 |
-
# db.commit()
|
32 |
-
|
33 |
-
# print(f"LLM analysis for job {job_id} completed successfully.")
|
34 |
-
|
35 |
-
# except Exception as e:
|
36 |
-
# print(f"Error during LLM analysis for job {job_id}: {e}")
|
37 |
-
# if job:
|
38 |
-
# job.status = "FAILED"
|
39 |
-
# error_data = job.result if job.result else {}
|
40 |
-
# error_data['error'] = f"LLM analysis failed: {str(e)}"
|
41 |
-
# job.result = error_data
|
42 |
-
# db.commit()
|
43 |
-
# finally:
|
44 |
-
# db.close()
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
# from celery_worker import celery
|
56 |
-
# from core.database import SessionLocal
|
57 |
-
# from models.analysis_job import AnalysisJob
|
58 |
-
# from tools.analyst_tools import get_llm_analysis
|
59 |
-
# from uuid import UUID
|
60 |
-
|
61 |
-
# @celery.task
|
62 |
-
# def run_llm_analysis(job_id: str):
|
63 |
-
# with SessionLocal() as db:
|
64 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
65 |
-
# if not job or not job.result:
|
66 |
-
# print(f"Job {job_id} not found or has no data for analyst.")
|
67 |
-
# return
|
68 |
-
|
69 |
-
# try:
|
70 |
-
# job.status = "ANALYZING"
|
71 |
-
# db.commit()
|
72 |
-
|
73 |
-
# current_data = job.result
|
74 |
-
# ticker = current_data.get("ticker")
|
75 |
-
# company_name = current_data.get("company_name")
|
76 |
-
# intelligence_briefing = current_data.get("intelligence_briefing", {})
|
77 |
-
|
78 |
-
# llm_report_data = get_llm_analysis(ticker, company_name, intelligence_briefing)
|
79 |
-
|
80 |
-
# new_result = dict(current_data)
|
81 |
-
# new_result['llm_analysis'] = llm_report_data
|
82 |
-
# job.result = new_result
|
83 |
-
|
84 |
-
# job.status = "SUCCESS"
|
85 |
-
# db.commit()
|
86 |
-
|
87 |
-
# print(f"LLM analysis for job {job_id} completed successfully.")
|
88 |
-
# return "LLM analysis successful."
|
89 |
-
# except Exception as e:
|
90 |
-
# print(f"Error during LLM analysis for job {job_id}: {e}")
|
91 |
-
# job.status = "FAILED"
|
92 |
-
# error_data = job.result if job.result else {}
|
93 |
-
# error_data['error'] = f"LLM analysis failed: {str(e)}"
|
94 |
-
# job.result = error_data
|
95 |
-
# db.commit()
|
96 |
-
# return f"LLM analysis failed: {e}"
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
from celery_worker import celery
|
109 |
from tools.analyst_tools import get_llm_analysis
|
110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from celery_worker import celery
|
2 |
from tools.analyst_tools import get_llm_analysis
|
3 |
|
backend/tasks/data_tasks.py
CHANGED
@@ -1,93 +1,3 @@
|
|
1 |
-
# from celery_worker import celery
|
2 |
-
# from core.database import SessionLocal
|
3 |
-
# from models.analysis_job import AnalysisJob
|
4 |
-
# from tools.data_tools import get_stock_data
|
5 |
-
# from uuid import UUID
|
6 |
-
|
7 |
-
# @celery.task
|
8 |
-
# def run_data_analysis(job_id: str, ticker: str):
|
9 |
-
# db = SessionLocal()
|
10 |
-
# job = None
|
11 |
-
# final_result = ""
|
12 |
-
# try:
|
13 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
14 |
-
# if not job:
|
15 |
-
# raise ValueError(f"Job {job_id} not found in database.")
|
16 |
-
|
17 |
-
# print(f"Status - DATA_FETCHING for job {job_id}...")
|
18 |
-
# job.status = "DATA_FETCHING"
|
19 |
-
# db.commit()
|
20 |
-
|
21 |
-
# data = get_stock_data(ticker)
|
22 |
-
|
23 |
-
# if "error" in data:
|
24 |
-
# raise ValueError(data["error"])
|
25 |
-
|
26 |
-
# job.result = data
|
27 |
-
# db.commit()
|
28 |
-
# print(f"Data analysis for job {job_id} completed successfully.")
|
29 |
-
|
30 |
-
# final_result = str(job.result)
|
31 |
-
|
32 |
-
# except Exception as e:
|
33 |
-
# print(f"Error during data analysis for job {job_id}: {e}")
|
34 |
-
# if job:
|
35 |
-
# job.status = "FAILED"
|
36 |
-
# job.result = {"error": f"Data analysis failed: {str(e)}"}
|
37 |
-
# db.commit()
|
38 |
-
# final_result = f"Error: {e}"
|
39 |
-
# finally:
|
40 |
-
# db.close()
|
41 |
-
|
42 |
-
# return final_result
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
# from celery_worker import celery
|
51 |
-
# from core.database import SessionLocal
|
52 |
-
# from models.analysis_job import AnalysisJob
|
53 |
-
# from tools.data_tools import get_stock_data
|
54 |
-
# from uuid import UUID
|
55 |
-
|
56 |
-
# @celery.task
|
57 |
-
# def run_data_analysis(job_id: str, ticker: str):
|
58 |
-
# with SessionLocal() as db:
|
59 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
60 |
-
# if not job:
|
61 |
-
# print(f"Job {job_id} not found.")
|
62 |
-
# return
|
63 |
-
|
64 |
-
# try:
|
65 |
-
# job.status = "DATA_FETCHING"
|
66 |
-
# db.commit()
|
67 |
-
|
68 |
-
# data = get_stock_data(ticker)
|
69 |
-
# if "error" in data:
|
70 |
-
# raise ValueError(data["error"])
|
71 |
-
|
72 |
-
# job.result = data
|
73 |
-
# db.commit()
|
74 |
-
# print(f"Data analysis for job {job_id} completed successfully.")
|
75 |
-
# return "Data fetching successful."
|
76 |
-
# except Exception as e:
|
77 |
-
# print(f"Error during data analysis for job {job_id}: {e}")
|
78 |
-
# job.status = "FAILED"
|
79 |
-
# job.result = {"error": f"Data analysis failed: {str(e)}"}
|
80 |
-
# db.commit()
|
81 |
-
# return f"Data fetching failed: {e}"
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
from celery_worker import celery
|
92 |
from tools.data_tools import get_stock_data
|
93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from celery_worker import celery
|
2 |
from tools.data_tools import get_stock_data
|
3 |
|
backend/tasks/main_task.py
CHANGED
@@ -1,113 +1,3 @@
|
|
1 |
-
# from celery_worker import celery
|
2 |
-
# from core.database import SessionLocal
|
3 |
-
# from models.analysis_job import AnalysisJob
|
4 |
-
# from tools.data_tools import get_stock_data
|
5 |
-
# from tools.news_tools import get_combined_news_and_sentiment
|
6 |
-
# from tools.analyst_tools import get_llm_analysis
|
7 |
-
# from uuid import UUID
|
8 |
-
# import json
|
9 |
-
|
10 |
-
# @celery.task
|
11 |
-
# def run_full_analysis(job_id: str, ticker: str):
|
12 |
-
# print(f"\n--- [START] Full Analysis for Job ID: {job_id} ---")
|
13 |
-
|
14 |
-
# # --- Stage 1: Data Fetching ---
|
15 |
-
# try:
|
16 |
-
# with SessionLocal() as db:
|
17 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
18 |
-
# if not job: raise ValueError("Job not found")
|
19 |
-
# job.status = "DATA_FETCHING"
|
20 |
-
# db.commit()
|
21 |
-
# print("[LOG] STATUS UPDATE: DATA_FETCHING")
|
22 |
-
|
23 |
-
# data_result = get_stock_data(ticker)
|
24 |
-
# if "error" in data_result: raise ValueError(data_result['error'])
|
25 |
-
# company_name = data_result.get("company_name", ticker)
|
26 |
-
|
27 |
-
# with SessionLocal() as db:
|
28 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
29 |
-
# job.result = data_result
|
30 |
-
# db.commit()
|
31 |
-
# db.refresh(job) # Force reload from DB
|
32 |
-
# print(f"[LOG] DB SAVE 1 (Data): Result keys are now: {list(job.result.keys())}")
|
33 |
-
|
34 |
-
# except Exception as e:
|
35 |
-
# print(f"!!! [FAILURE] Stage 1 (Data): {e}")
|
36 |
-
# # ... error handling ...
|
37 |
-
# return
|
38 |
-
|
39 |
-
# # --- Stage 2: Intelligence Gathering ---
|
40 |
-
# try:
|
41 |
-
# with SessionLocal() as db:
|
42 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
43 |
-
# job.status = "INTELLIGENCE_GATHERING"
|
44 |
-
# db.commit()
|
45 |
-
# print("[LOG] STATUS UPDATE: INTELLIGENCE_GATHERING")
|
46 |
-
|
47 |
-
# intelligence_result = get_combined_news_and_sentiment(ticker, company_name)
|
48 |
-
|
49 |
-
# with SessionLocal() as db:
|
50 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
51 |
-
# current_result = dict(job.result)
|
52 |
-
# current_result['intelligence_briefing'] = intelligence_result
|
53 |
-
# job.result = current_result
|
54 |
-
# db.commit()
|
55 |
-
# db.refresh(job) # Force reload
|
56 |
-
# print(f"[LOG] DB SAVE 2 (Intelligence): Result keys are now: {list(job.result.keys())}")
|
57 |
-
# except Exception as e:
|
58 |
-
# print(f"!!! [FAILURE] Stage 2 (Intelligence): {e}")
|
59 |
-
# # ... error handling ...
|
60 |
-
# return
|
61 |
-
|
62 |
-
# # --- Stage 3: LLM Analysis ---
|
63 |
-
# try:
|
64 |
-
# with SessionLocal() as db:
|
65 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
66 |
-
# job.status = "ANALYZING"
|
67 |
-
# db.commit()
|
68 |
-
# print("[LOG] STATUS UPDATE: ANALYZING")
|
69 |
-
|
70 |
-
# with SessionLocal() as db:
|
71 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
72 |
-
# data_for_llm = job.result
|
73 |
-
|
74 |
-
# llm_result = get_llm_analysis(ticker, company_name, data_for_llm.get("intelligence_briefing", {}))
|
75 |
-
# if "error" in llm_result: raise ValueError(llm_result['error'])
|
76 |
-
|
77 |
-
# # --- Final Assembly and Save ---
|
78 |
-
# print("[LOG] Finalizing results...")
|
79 |
-
# with SessionLocal() as db:
|
80 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
81 |
-
# final_result_data = dict(job.result)
|
82 |
-
# final_result_data['llm_analysis'] = llm_result
|
83 |
-
# job.result = final_result_data
|
84 |
-
# job.status = "SUCCESS"
|
85 |
-
# db.commit()
|
86 |
-
# db.refresh(job)
|
87 |
-
# print(f"[LOG] DB SAVE 3 (Final): Result keys are now: {list(job.result.keys())}")
|
88 |
-
|
89 |
-
# print(f"--- [SUCCESS] Full analysis for {job_id} complete. ---")
|
90 |
-
|
91 |
-
# except Exception as e:
|
92 |
-
# print(f"!!! [FAILURE] Stage 3 (LLM): {e}")
|
93 |
-
# with SessionLocal() as db:
|
94 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
95 |
-
# if job:
|
96 |
-
# job.status = "FAILED"
|
97 |
-
# error_data = job.result if job.result else {}
|
98 |
-
# error_data['error'] = str(e)
|
99 |
-
# job.result = error_data
|
100 |
-
# db.commit()
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
from celery_worker import celery
|
112 |
from core.database import SessionLocal
|
113 |
from models.analysis_job import AnalysisJob
|
@@ -123,9 +13,7 @@ def run_full_analysis(job_id: str, ticker: str):
|
|
123 |
The single, main task that orchestrates the entire analysis pipeline.
|
124 |
"""
|
125 |
print(f"\n--- [START] Full Analysis for Job ID: {job_id} ---")
|
126 |
-
|
127 |
-
# We will use one job object throughout and update it, committing as we go.
|
128 |
-
# This requires careful session management.
|
129 |
db = SessionLocal()
|
130 |
job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from celery_worker import celery
|
2 |
from core.database import SessionLocal
|
3 |
from models.analysis_job import AnalysisJob
|
|
|
13 |
The single, main task that orchestrates the entire analysis pipeline.
|
14 |
"""
|
15 |
print(f"\n--- [START] Full Analysis for Job ID: {job_id} ---")
|
16 |
+
|
|
|
|
|
17 |
db = SessionLocal()
|
18 |
job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
19 |
|
backend/tasks/news_tasks.py
CHANGED
@@ -1,348 +1,7 @@
|
|
1 |
-
# # tasks/news_tasks.py - SIMPLIFIED VERSION THAT ALWAYS WORKS
|
2 |
-
|
3 |
-
# from celery_worker import celery
|
4 |
-
# from core.database import SessionLocal
|
5 |
-
# from models.analysis_job import AnalysisJob
|
6 |
-
# from uuid import UUID
|
7 |
-
# import logging
|
8 |
-
# from datetime import datetime
|
9 |
-
# import yfinance as yf
|
10 |
-
|
11 |
-
# logger = logging.getLogger(__name__)
|
12 |
-
|
13 |
-
# def get_stock_basic_info(ticker: str):
|
14 |
-
# """Get basic stock information to create realistic content"""
|
15 |
-
# try:
|
16 |
-
# stock = yf.Ticker(ticker)
|
17 |
-
# info = stock.info
|
18 |
-
# return {
|
19 |
-
# 'name': info.get('longName', ticker.replace('.NS', '')),
|
20 |
-
# 'sector': info.get('sector', 'Unknown'),
|
21 |
-
# 'industry': info.get('industry', 'Unknown'),
|
22 |
-
# 'current_price': info.get('currentPrice', 0),
|
23 |
-
# 'previous_close': info.get('previousClose', 0)
|
24 |
-
# }
|
25 |
-
# except Exception as e:
|
26 |
-
# logger.warning(f"Could not get stock info for {ticker}: {e}")
|
27 |
-
# return {
|
28 |
-
# 'name': ticker.replace('.NS', ''),
|
29 |
-
# 'sector': 'Unknown',
|
30 |
-
# 'industry': 'Unknown',
|
31 |
-
# 'current_price': 0,
|
32 |
-
# 'previous_close': 0
|
33 |
-
# }
|
34 |
-
|
35 |
-
# def create_realistic_articles(ticker: str, company_name: str, stock_info: dict):
|
36 |
-
# """Create realistic articles based on stock information"""
|
37 |
-
|
38 |
-
# # Calculate price movement for realistic sentiment
|
39 |
-
# current_price = stock_info.get('current_price', 0)
|
40 |
-
# previous_close = stock_info.get('previous_close', 0)
|
41 |
-
|
42 |
-
# price_change = 0
|
43 |
-
# if current_price and previous_close:
|
44 |
-
# price_change = ((current_price - previous_close) / previous_close) * 100
|
45 |
-
|
46 |
-
# # Generate articles based on actual stock performance
|
47 |
-
# articles = []
|
48 |
-
|
49 |
-
# if price_change > 2:
|
50 |
-
# articles.extend([
|
51 |
-
# {
|
52 |
-
# "title": f"{company_name} Shares Rally {price_change:.1f}% on Strong Market Sentiment",
|
53 |
-
# "url": f"https://finance.yahoo.com/quote/{ticker}",
|
54 |
-
# "source": "Market Analysis",
|
55 |
-
# "sentiment": "Positive",
|
56 |
-
# "sentiment_score": 0.8
|
57 |
-
# },
|
58 |
-
# {
|
59 |
-
# "title": f"Investors Show Confidence in {company_name} as Stock Gains Momentum",
|
60 |
-
# "url": f"https://www.moneycontrol.com/india/stockpricequote/{ticker}",
|
61 |
-
# "source": "Financial Express",
|
62 |
-
# "sentiment": "Positive",
|
63 |
-
# "sentiment_score": 0.7
|
64 |
-
# }
|
65 |
-
# ])
|
66 |
-
# elif price_change < -2:
|
67 |
-
# articles.extend([
|
68 |
-
# {
|
69 |
-
# "title": f"{company_name} Stock Declines {abs(price_change):.1f}% Amid Market Volatility",
|
70 |
-
# "url": f"https://finance.yahoo.com/quote/{ticker}",
|
71 |
-
# "source": "Market Watch",
|
72 |
-
# "sentiment": "Negative",
|
73 |
-
# "sentiment_score": 0.8
|
74 |
-
# },
|
75 |
-
# {
|
76 |
-
# "title": f"Market Correction Impacts {company_name} Share Price",
|
77 |
-
# "url": f"https://www.moneycontrol.com/india/stockpricequote/{ticker}",
|
78 |
-
# "source": "Economic Times",
|
79 |
-
# "sentiment": "Negative",
|
80 |
-
# "sentiment_score": 0.6
|
81 |
-
# }
|
82 |
-
# ])
|
83 |
-
# else:
|
84 |
-
# articles.extend([
|
85 |
-
# {
|
86 |
-
# "title": f"{company_name} Stock Shows Steady Performance in Current Market",
|
87 |
-
# "url": f"https://finance.yahoo.com/quote/{ticker}",
|
88 |
-
# "source": "Yahoo Finance",
|
89 |
-
# "sentiment": "Neutral",
|
90 |
-
# "sentiment_score": 0.5
|
91 |
-
# },
|
92 |
-
# {
|
93 |
-
# "title": f"Technical Analysis: {company_name} Maintains Stable Trading Range",
|
94 |
-
# "url": f"https://www.moneycontrol.com/india/stockpricequote/{ticker}",
|
95 |
-
# "source": "Market Analysis",
|
96 |
-
# "sentiment": "Neutral",
|
97 |
-
# "sentiment_score": 0.5
|
98 |
-
# }
|
99 |
-
# ])
|
100 |
-
|
101 |
-
# # Add sector-specific articles
|
102 |
-
# sector = stock_info.get('sector', 'Unknown')
|
103 |
-
# if sector != 'Unknown':
|
104 |
-
# articles.extend([
|
105 |
-
# {
|
106 |
-
# "title": f"{sector} Sector Update: Key Players Including {company_name} in Focus",
|
107 |
-
# "url": "https://example.com/sector-analysis",
|
108 |
-
# "source": "Sector Reports",
|
109 |
-
# "sentiment": "Neutral",
|
110 |
-
# "sentiment_score": 0.6
|
111 |
-
# },
|
112 |
-
# {
|
113 |
-
# "title": f"Industry Outlook: {stock_info.get('industry', 'Market')} Trends Affecting {company_name}",
|
114 |
-
# "url": "https://example.com/industry-outlook",
|
115 |
-
# "source": "Industry Analysis",
|
116 |
-
# "sentiment": "Positive",
|
117 |
-
# "sentiment_score": 0.6
|
118 |
-
# }
|
119 |
-
# ])
|
120 |
-
|
121 |
-
# # Add general market articles
|
122 |
-
# articles.extend([
|
123 |
-
# {
|
124 |
-
# "title": f"Quarterly Performance Review: {company_name} Financials and Market Position",
|
125 |
-
# "url": f"https://finance.yahoo.com/quote/{ticker}/financials",
|
126 |
-
# "source": "Financial Reports",
|
127 |
-
# "sentiment": "Neutral",
|
128 |
-
# "sentiment_score": 0.5
|
129 |
-
# },
|
130 |
-
# {
|
131 |
-
# "title": f"Analyst Coverage: Investment Recommendations for {company_name} Stock",
|
132 |
-
# "url": "https://example.com/analyst-coverage",
|
133 |
-
# "source": "Research Reports",
|
134 |
-
# "sentiment": "Positive",
|
135 |
-
# "sentiment_score": 0.7
|
136 |
-
# },
|
137 |
-
# {
|
138 |
-
# "title": f"Market Sentiment Analysis: Retail vs Institutional Interest in {company_name}",
|
139 |
-
# "url": "https://example.com/market-sentiment",
|
140 |
-
# "source": "Market Research",
|
141 |
-
# "sentiment": "Neutral",
|
142 |
-
# "sentiment_score": 0.5
|
143 |
-
# }
|
144 |
-
# ])
|
145 |
-
|
146 |
-
# return articles[:8] # Return top 8 articles
|
147 |
-
|
148 |
-
# def try_real_news_sources(ticker: str, company_name: str):
|
149 |
-
# """Attempt to get real news, but don't fail if it doesn't work"""
|
150 |
-
# real_articles = []
|
151 |
-
|
152 |
-
# try:
|
153 |
-
# # Try Yahoo Finance news (most reliable)
|
154 |
-
# logger.info(f"Attempting to fetch real Yahoo Finance news for {ticker}")
|
155 |
-
# stock = yf.Ticker(ticker)
|
156 |
-
# news = stock.news
|
157 |
-
|
158 |
-
# if news:
|
159 |
-
# logger.info(f"Found {len(news)} Yahoo Finance articles")
|
160 |
-
# for article in news[:5]: # Take first 5
|
161 |
-
# if article.get('title'):
|
162 |
-
# # Simple sentiment analysis
|
163 |
-
# title_lower = article['title'].lower()
|
164 |
-
# if any(word in title_lower for word in ['gain', 'rise', 'growth', 'profit', 'strong']):
|
165 |
-
# sentiment = 'Positive'
|
166 |
-
# score = 0.7
|
167 |
-
# elif any(word in title_lower for word in ['fall', 'decline', 'loss', 'weak', 'drop']):
|
168 |
-
# sentiment = 'Negative'
|
169 |
-
# score = 0.7
|
170 |
-
# else:
|
171 |
-
# sentiment = 'Neutral'
|
172 |
-
# score = 0.5
|
173 |
-
|
174 |
-
# real_articles.append({
|
175 |
-
# "title": article['title'].strip(),
|
176 |
-
# "url": article.get('link', ''),
|
177 |
-
# "source": article.get('publisher', 'Yahoo Finance'),
|
178 |
-
# "sentiment": sentiment,
|
179 |
-
# "sentiment_score": score,
|
180 |
-
# "is_real": True
|
181 |
-
# })
|
182 |
-
|
183 |
-
# logger.info(f"Successfully retrieved {len(real_articles)} real articles")
|
184 |
-
|
185 |
-
# except Exception as e:
|
186 |
-
# logger.warning(f"Could not fetch real news: {e}")
|
187 |
-
|
188 |
-
# return real_articles
|
189 |
-
|
190 |
-
# @celery.task
|
191 |
-
# def run_intelligence_analysis(job_id: str):
|
192 |
-
# """Simplified intelligence analysis that always provides results"""
|
193 |
-
# db = SessionLocal()
|
194 |
-
# job = None
|
195 |
-
|
196 |
-
# try:
|
197 |
-
# logger.info(f"Starting intelligence analysis for job {job_id}")
|
198 |
-
|
199 |
-
# # Get job
|
200 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
201 |
-
# if not job or not job.result:
|
202 |
-
# raise ValueError(f"Job {job_id} not found or has no initial data.")
|
203 |
-
|
204 |
-
# job.status = "INTELLIGENCE_GATHERING"
|
205 |
-
# db.commit()
|
206 |
-
|
207 |
-
# current_data = job.result
|
208 |
-
# ticker = current_data.get("ticker")
|
209 |
-
# company_name = current_data.get("company_name", ticker.replace('.NS', ''))
|
210 |
-
|
211 |
-
# logger.info(f"Analyzing {company_name} ({ticker})")
|
212 |
-
|
213 |
-
# # Get basic stock information
|
214 |
-
# stock_info = get_stock_basic_info(ticker)
|
215 |
-
# logger.info(f"Stock info: {stock_info['name']} - {stock_info['sector']}")
|
216 |
-
|
217 |
-
# # Try to get real news first
|
218 |
-
# real_articles = try_real_news_sources(ticker, company_name)
|
219 |
-
|
220 |
-
# # Create realistic articles
|
221 |
-
# realistic_articles = create_realistic_articles(ticker, company_name, stock_info)
|
222 |
-
|
223 |
-
# # Combine real and realistic articles
|
224 |
-
# all_articles = real_articles + realistic_articles
|
225 |
-
|
226 |
-
# # Remove duplicates and limit to 10 articles
|
227 |
-
# seen_titles = set()
|
228 |
-
# unique_articles = []
|
229 |
-
# for article in all_articles:
|
230 |
-
# if article['title'] not in seen_titles:
|
231 |
-
# seen_titles.add(article['title'])
|
232 |
-
# unique_articles.append(article)
|
233 |
-
|
234 |
-
# final_articles = unique_articles[:10]
|
235 |
-
|
236 |
-
# # Count sentiments
|
237 |
-
# sentiment_counts = {'Positive': 0, 'Negative': 0, 'Neutral': 0}
|
238 |
-
# for article in final_articles:
|
239 |
-
# sentiment_counts[article['sentiment']] += 1
|
240 |
-
|
241 |
-
# # Create intelligence briefing
|
242 |
-
# intelligence_briefing = {
|
243 |
-
# "articles": final_articles,
|
244 |
-
# "sentiment_summary": {
|
245 |
-
# "total_items": len(final_articles),
|
246 |
-
# "positive": sentiment_counts['Positive'],
|
247 |
-
# "negative": sentiment_counts['Negative'],
|
248 |
-
# "neutral": sentiment_counts['Neutral'],
|
249 |
-
# "real_articles": len(real_articles),
|
250 |
-
# "generated_articles": len(realistic_articles),
|
251 |
-
# "analysis_timestamp": datetime.now().isoformat()
|
252 |
-
# }
|
253 |
-
# }
|
254 |
-
|
255 |
-
# # Update job result
|
256 |
-
# new_result = current_data.copy()
|
257 |
-
# new_result['intelligence_briefing'] = intelligence_briefing
|
258 |
-
# job.result = new_result
|
259 |
-
# job.status = "INTELLIGENCE_COMPLETE"
|
260 |
-
|
261 |
-
# db.commit()
|
262 |
-
|
263 |
-
# logger.info(f"Intelligence analysis completed successfully:")
|
264 |
-
# logger.info(f"- Total articles: {len(final_articles)}")
|
265 |
-
# logger.info(f"- Real articles: {len(real_articles)}")
|
266 |
-
# logger.info(f"- Generated articles: {len(realistic_articles)}")
|
267 |
-
# logger.info(f"- Sentiment: {sentiment_counts}")
|
268 |
-
|
269 |
-
# return str(job.result)
|
270 |
-
|
271 |
-
# except Exception as e:
|
272 |
-
# logger.error(f"Intelligence analysis failed for job {job_id}: {e}")
|
273 |
-
|
274 |
-
# if job:
|
275 |
-
# job.status = "FAILED"
|
276 |
-
# error_data = job.result if job.result else {}
|
277 |
-
# error_data['error'] = f"Intelligence analysis failed: {str(e)}"
|
278 |
-
# job.result = error_data
|
279 |
-
# db.commit()
|
280 |
-
|
281 |
-
# return f"Error: {e}"
|
282 |
-
|
283 |
-
# finally:
|
284 |
-
# db.close()
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
# from celery_worker import celery
|
295 |
-
# from core.database import SessionLocal
|
296 |
-
# from models.analysis_job import AnalysisJob
|
297 |
-
# from tools.news_tools import get_combined_news_and_sentiment
|
298 |
-
# from uuid import UUID
|
299 |
-
|
300 |
-
# @celery.task
|
301 |
-
# def run_intelligence_analysis(job_id: str):
|
302 |
-
# with SessionLocal() as db:
|
303 |
-
# job = db.query(AnalysisJob).filter(AnalysisJob.id == UUID(job_id)).first()
|
304 |
-
# if not job or not job.result:
|
305 |
-
# print(f"Job {job_id} not found or has no data for intelligence.")
|
306 |
-
# return
|
307 |
-
|
308 |
-
# try:
|
309 |
-
# job.status = "INTELLIGENCE_GATHERING"
|
310 |
-
# db.commit()
|
311 |
-
|
312 |
-
# current_data = job.result
|
313 |
-
# ticker = current_data.get("ticker")
|
314 |
-
# company_name = current_data.get("company_name")
|
315 |
-
|
316 |
-
# intelligence_briefing = get_combined_news_and_sentiment(ticker, company_name)
|
317 |
-
|
318 |
-
# new_result = dict(current_data)
|
319 |
-
# new_result['intelligence_briefing'] = intelligence_briefing
|
320 |
-
# job.result = new_result
|
321 |
-
|
322 |
-
# db.commit()
|
323 |
-
# print(f"Intelligence analysis for job {job_id} completed successfully.")
|
324 |
-
# return "Intelligence gathering successful."
|
325 |
-
# except Exception as e:
|
326 |
-
# print(f"Error during intelligence analysis for job {job_id}: {e}")
|
327 |
-
# job.status = "FAILED"
|
328 |
-
# error_data = job.result if job.result else {}
|
329 |
-
# error_data['error'] = f"Intelligence analysis failed: {str(e)}"
|
330 |
-
# job.result = error_data
|
331 |
-
# db.commit()
|
332 |
-
# return f"Intelligence gathering failed: {e}"
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
from celery_worker import celery
|
342 |
from tools.news_tools import get_combined_news_and_sentiment
|
343 |
|
344 |
@celery.task
|
345 |
def get_intelligence_task(ticker: str, company_name: str):
|
346 |
print(f"Executing get_intelligence_task for {company_name}...")
|
347 |
-
# This task now depends on the company_name from the first task's result
|
348 |
return get_combined_news_and_sentiment(ticker, company_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from celery_worker import celery
|
2 |
from tools.news_tools import get_combined_news_and_sentiment
|
3 |
|
4 |
@celery.task
|
5 |
def get_intelligence_task(ticker: str, company_name: str):
|
6 |
print(f"Executing get_intelligence_task for {company_name}...")
|
|
|
7 |
return get_combined_news_and_sentiment(ticker, company_name)
|
backend/tmp.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1 |
-
# test_news.py - Run this to test the news functionality
|
2 |
from tools.news_tools import get_combined_news_and_sentiment_debug
|
3 |
|
4 |
def test_news():
|
5 |
-
# Test with a popular Indian stock
|
6 |
ticker = "RELIANCE.NS"
|
7 |
company_name = "Reliance Industries"
|
8 |
|
|
|
|
|
1 |
from tools.news_tools import get_combined_news_and_sentiment_debug
|
2 |
|
3 |
def test_news():
|
|
|
4 |
ticker = "RELIANCE.NS"
|
5 |
company_name = "Reliance Industries"
|
6 |
|
backend/tools/prediction_tools.py
CHANGED
@@ -11,14 +11,10 @@ def generate_forecast(ticker: str) -> Dict[str, Any]:
|
|
11 |
if stock_data.empty:
|
12 |
return {"error": f"Could not download historical data for {ticker}."}
|
13 |
|
14 |
-
# --- THE FINAL, MOST ROBUST FIX FOR THE DATAFRAME ---
|
15 |
-
# 1. Create a new DataFrame with only the columns we need.
|
16 |
df_prophet = stock_data[['Close']].copy()
|
17 |
-
# 2. Reset the index to turn the 'Date' index into a column.
|
18 |
df_prophet.reset_index(inplace=True)
|
19 |
# 3. Rename the columns to what Prophet expects.
|
20 |
df_prophet.columns = ['ds', 'y']
|
21 |
-
# --- END OF FIX ---
|
22 |
|
23 |
model = Prophet(
|
24 |
daily_seasonality=False,
|
|
|
11 |
if stock_data.empty:
|
12 |
return {"error": f"Could not download historical data for {ticker}."}
|
13 |
|
|
|
|
|
14 |
df_prophet = stock_data[['Close']].copy()
|
|
|
15 |
df_prophet.reset_index(inplace=True)
|
16 |
# 3. Rename the columns to what Prophet expects.
|
17 |
df_prophet.columns = ['ds', 'y']
|
|
|
18 |
|
19 |
model = Prophet(
|
20 |
daily_seasonality=False,
|
docker-compose.yml
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
services:
|
2 |
-
# --- Application Services ---
|
3 |
redis:
|
4 |
image: redis:7-alpine
|
5 |
ports:
|
@@ -53,53 +52,3 @@ services:
|
|
53 |
|
54 |
|
55 |
|
56 |
-
|
57 |
-
|
58 |
-
# services:
|
59 |
-
# redis:
|
60 |
-
# image: redis:7-alpine
|
61 |
-
# ports:
|
62 |
-
# - "6379:6379"
|
63 |
-
# restart: always
|
64 |
-
|
65 |
-
# backend:
|
66 |
-
# build:
|
67 |
-
# context: .
|
68 |
-
# dockerfile: ./backend/Dockerfile
|
69 |
-
# ports:
|
70 |
-
# - "8000:8000"
|
71 |
-
# volumes:
|
72 |
-
# - ./backend:/code/app
|
73 |
-
# env_file:
|
74 |
-
# - .env
|
75 |
-
# command: python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
76 |
-
# restart: always
|
77 |
-
# depends_on:
|
78 |
-
# - redis
|
79 |
-
|
80 |
-
# worker:
|
81 |
-
# build:
|
82 |
-
# context: .
|
83 |
-
# dockerfile: ./backend/Dockerfile
|
84 |
-
# volumes:
|
85 |
-
# - ./backend:/code/app
|
86 |
-
# env_file:
|
87 |
-
# - .env
|
88 |
-
# command: python -m celery -A celery_worker.celery worker --loglevel=info
|
89 |
-
# restart: always
|
90 |
-
# depends_on:
|
91 |
-
# - redis
|
92 |
-
# - backend
|
93 |
-
|
94 |
-
# frontend:
|
95 |
-
# build:
|
96 |
-
# context: .
|
97 |
-
# dockerfile: ./frontend/Dockerfile
|
98 |
-
# ports:
|
99 |
-
# - "5173:5173"
|
100 |
-
# volumes:
|
101 |
-
# - ./frontend:/app
|
102 |
-
# - /app/node_modules
|
103 |
-
# restart: always
|
104 |
-
# depends_on:
|
105 |
-
# - backend
|
|
|
1 |
services:
|
|
|
2 |
redis:
|
3 |
image: redis:7-alpine
|
4 |
ports:
|
|
|
52 |
|
53 |
|
54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/App.jsx
CHANGED
@@ -1,125 +1,3 @@
|
|
1 |
-
// import React, { useState, useEffect } from 'react';
|
2 |
-
// import Header from './components/Header';
|
3 |
-
// import JobForm from './components/JobForm';
|
4 |
-
// import JobStatusCard from './components/JobStatusCard';
|
5 |
-
// import ResultsDisplay from './components/ResultsDisplay';
|
6 |
-
// import LoadingSkeleton from './components/LoadingSkeleton';
|
7 |
-
// import HistoryPanel from './components/HistoryPanel';
|
8 |
-
// import { createJob, getJob } from './services/api';
|
9 |
-
// import { XCircle } from 'lucide-react';
|
10 |
-
|
11 |
-
// function App() {
|
12 |
-
// const [job, setJob] = useState(null);
|
13 |
-
// const [isLoading, setIsLoading] = useState(false);
|
14 |
-
// const [isPolling, setIsPolling] = useState(false);
|
15 |
-
// const [error, setError] = useState(null);
|
16 |
-
|
17 |
-
// const handleAnalysisRequest = async (ticker) => {
|
18 |
-
// setIsLoading(true);
|
19 |
-
// setIsPolling(true);
|
20 |
-
// setError(null);
|
21 |
-
// setJob(null);
|
22 |
-
// try {
|
23 |
-
// const response = await createJob(ticker);
|
24 |
-
// setJob(response.data);
|
25 |
-
// } catch (err) {
|
26 |
-
// setError('Failed to create job. Please check the API server and try again.');
|
27 |
-
// setIsLoading(false);
|
28 |
-
// setIsPolling(false);
|
29 |
-
// }
|
30 |
-
// };
|
31 |
-
|
32 |
-
// const handleSelectHistoryJob = (historyJob) => {
|
33 |
-
// setIsLoading(false);
|
34 |
-
// setIsPolling(false);
|
35 |
-
// setError(null);
|
36 |
-
// setJob(historyJob);
|
37 |
-
// }
|
38 |
-
|
39 |
-
// useEffect(() => {
|
40 |
-
// if (!job?.id || !isPolling) return;
|
41 |
-
|
42 |
-
// if (job.status !== 'PENDING') {
|
43 |
-
// setIsLoading(false);
|
44 |
-
// }
|
45 |
-
|
46 |
-
// const intervalId = setInterval(async () => {
|
47 |
-
// try {
|
48 |
-
// const response = await getJob(job.id);
|
49 |
-
// const updatedJob = response.data;
|
50 |
-
// setJob(updatedJob);
|
51 |
-
|
52 |
-
// if (updatedJob.status === 'SUCCESS' || updatedJob.status === 'FAILED') {
|
53 |
-
// clearInterval(intervalId);
|
54 |
-
// setIsPolling(false);
|
55 |
-
// }
|
56 |
-
// } catch (err) {
|
57 |
-
// setError('Failed to poll job status.');
|
58 |
-
// clearInterval(intervalId);
|
59 |
-
// setIsPolling(false);
|
60 |
-
// }
|
61 |
-
// }, 3000);
|
62 |
-
|
63 |
-
// return () => clearInterval(intervalId);
|
64 |
-
// }, [job, isPolling]);
|
65 |
-
|
66 |
-
// return (
|
67 |
-
// <div className="min-h-screen bg-gray-900 text-white font-sans">
|
68 |
-
// <Header />
|
69 |
-
// <HistoryPanel onSelectJob={handleSelectHistoryJob} />
|
70 |
-
|
71 |
-
// <main className="container mx-auto p-4 md:p-8">
|
72 |
-
// <div className="max-w-4xl mx-auto">
|
73 |
-
// <p className="text-lg text-gray-400 mb-8 text-center">
|
74 |
-
// Enter an Indian stock ticker to receive a comprehensive, AI-powered analysis.
|
75 |
-
// </p>
|
76 |
-
|
77 |
-
// <JobForm onAnalyze={handleAnalysisRequest} isLoading={isLoading || isPolling} />
|
78 |
-
|
79 |
-
// {error && <div className="my-6 p-4 bg-red-900/50 rounded-lg text-red-300 text-center">{error}</div>}
|
80 |
-
|
81 |
-
// {isLoading && !job && <LoadingSkeleton />}
|
82 |
-
|
83 |
-
// {job && !isLoading && <JobStatusCard job={job} />}
|
84 |
-
|
85 |
-
// {job?.status === 'SUCCESS' && job.result && (
|
86 |
-
// <ResultsDisplay result={job.result} />
|
87 |
-
// )}
|
88 |
-
|
89 |
-
// {job?.status === 'FAILED' && job.result?.error && (
|
90 |
-
// <div className="mt-8 p-6 bg-gray-800/30 border border-red-500/30 rounded-lg text-center animate-fade-in">
|
91 |
-
// <XCircle className="w-16 h-16 text-red-400 mx-auto mb-4" />
|
92 |
-
// <h2 className="text-2xl font-bold text-red-300 mb-2">Analysis Failed</h2>
|
93 |
-
// <p className="text-gray-400 max-w-lg mx-auto">
|
94 |
-
// We couldn't complete the analysis for <strong className="text-white">{job.ticker}</strong>.
|
95 |
-
// This usually means the stock symbol is incorrect or not listed.
|
96 |
-
// </p>
|
97 |
-
// <p className="text-xs text-gray-500 mt-4">Please double-check the ticker (e.g., RELIANCE.NS) and try again.</p>
|
98 |
-
|
99 |
-
// <details className="mt-6 text-left w-full max-w-lg mx-auto">
|
100 |
-
// <summary className="cursor-pointer text-xs text-gray-500 hover:text-gray-400 focus:outline-none">Show technical details</summary>
|
101 |
-
// <pre className="mt-2 bg-gray-900 p-4 rounded-md text-gray-400 text-xs whitespace-pre-wrap font-mono">
|
102 |
-
// {job.result.error}
|
103 |
-
// </pre>
|
104 |
-
// </details>
|
105 |
-
// </div>
|
106 |
-
// )}
|
107 |
-
// </div>
|
108 |
-
// </main>
|
109 |
-
// </div>
|
110 |
-
// );
|
111 |
-
// }
|
112 |
-
|
113 |
-
// export default App;
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
import React, { useState, useEffect } from 'react';
|
124 |
import Header from './components/Header';
|
125 |
import JobForm from './components/JobForm';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import React, { useState, useEffect } from 'react';
|
2 |
import Header from './components/Header';
|
3 |
import JobForm from './components/JobForm';
|
frontend/src/components/Header.jsx
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
import React from 'react';
|
2 |
-
import { Bot } from 'lucide-react';
|
3 |
|
4 |
function Header() {
|
5 |
return (
|
|
|
1 |
import React from 'react';
|
2 |
+
import { Bot } from 'lucide-react';
|
3 |
|
4 |
function Header() {
|
5 |
return (
|
frontend/src/components/HistoricalChart.jsx
CHANGED
@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
|
|
2 |
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
3 |
import axios from 'axios';
|
4 |
|
5 |
-
// A more reliable public CORS proxy
|
6 |
const PROXY_URL = 'https://api.allorigins.win/raw?url=';
|
7 |
|
8 |
const fetchHistoricalData = async (ticker) => {
|
@@ -15,7 +14,6 @@ const fetchHistoricalData = async (ticker) => {
|
|
15 |
const timestamps = data.timestamp;
|
16 |
const prices = data.indicators.quote[0].close;
|
17 |
|
18 |
-
// Filter out any null price points which can crash the chart
|
19 |
return timestamps
|
20 |
.map((ts, i) => ({
|
21 |
date: new Date(ts * 1000).toLocaleDateString('en-IN', {day: 'numeric', month: 'short'}),
|
|
|
2 |
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
3 |
import axios from 'axios';
|
4 |
|
|
|
5 |
const PROXY_URL = 'https://api.allorigins.win/raw?url=';
|
6 |
|
7 |
const fetchHistoricalData = async (ticker) => {
|
|
|
14 |
const timestamps = data.timestamp;
|
15 |
const prices = data.indicators.quote[0].close;
|
16 |
|
|
|
17 |
return timestamps
|
18 |
.map((ts, i) => ({
|
19 |
date: new Date(ts * 1000).toLocaleDateString('en-IN', {day: 'numeric', month: 'short'}),
|
frontend/src/components/HistoryPanel.jsx
CHANGED
@@ -11,14 +11,12 @@ function HistoryPanel({ onSelectJob }) {
|
|
11 |
setIsLoading(true);
|
12 |
getJobsHistory()
|
13 |
.then(response => {
|
14 |
-
// Filter for only completed jobs to make the list cleaner
|
15 |
setHistory(response.data.filter(job => job.status === 'SUCCESS' || job.status === 'FAILED'));
|
16 |
})
|
17 |
.catch(error => console.error("Failed to fetch history:", error))
|
18 |
.finally(() => setIsLoading(false));
|
19 |
};
|
20 |
|
21 |
-
// When the panel opens, fetch the history
|
22 |
const togglePanel = () => {
|
23 |
const newIsOpen = !isOpen;
|
24 |
setIsOpen(newIsOpen);
|
@@ -42,7 +40,6 @@ function HistoryPanel({ onSelectJob }) {
|
|
42 |
<History className="w-8 h-8" />
|
43 |
</button>
|
44 |
|
45 |
-
{/* Overlay to close panel when clicking outside */}
|
46 |
{isOpen && <div onClick={() => setIsOpen(false)} className="fixed inset-0 bg-black/50 z-30 transition-opacity"></div>}
|
47 |
|
48 |
<div className={`fixed top-0 right-0 h-full bg-gray-900 border-l border-gray-700 shadow-2xl z-40 transition-transform duration-500 ease-in-out ${isOpen ? 'translate-x-0' : 'translate-x-full'} w-full md:w-96`}>
|
|
|
11 |
setIsLoading(true);
|
12 |
getJobsHistory()
|
13 |
.then(response => {
|
|
|
14 |
setHistory(response.data.filter(job => job.status === 'SUCCESS' || job.status === 'FAILED'));
|
15 |
})
|
16 |
.catch(error => console.error("Failed to fetch history:", error))
|
17 |
.finally(() => setIsLoading(false));
|
18 |
};
|
19 |
|
|
|
20 |
const togglePanel = () => {
|
21 |
const newIsOpen = !isOpen;
|
22 |
setIsOpen(newIsOpen);
|
|
|
40 |
<History className="w-8 h-8" />
|
41 |
</button>
|
42 |
|
|
|
43 |
{isOpen && <div onClick={() => setIsOpen(false)} className="fixed inset-0 bg-black/50 z-30 transition-opacity"></div>}
|
44 |
|
45 |
<div className={`fixed top-0 right-0 h-full bg-gray-900 border-l border-gray-700 shadow-2xl z-40 transition-transform duration-500 ease-in-out ${isOpen ? 'translate-x-0' : 'translate-x-full'} w-full md:w-96`}>
|
frontend/src/components/JobForm.jsx
CHANGED
@@ -1,14 +1,13 @@
|
|
1 |
import React, { useState } from 'react';
|
2 |
import { Search, LoaderCircle } from 'lucide-react';
|
3 |
|
4 |
-
// The component now receives 'onAnalyze' and 'isLoading' as props
|
5 |
function JobForm({ onAnalyze, isLoading }) {
|
6 |
const [ticker, setTicker] = useState('');
|
7 |
|
8 |
const handleSubmit = (e) => {
|
9 |
e.preventDefault();
|
10 |
if (!ticker.trim() || isLoading) return;
|
11 |
-
onAnalyze(ticker);
|
12 |
};
|
13 |
|
14 |
return (
|
|
|
1 |
import React, { useState } from 'react';
|
2 |
import { Search, LoaderCircle } from 'lucide-react';
|
3 |
|
|
|
4 |
function JobForm({ onAnalyze, isLoading }) {
|
5 |
const [ticker, setTicker] = useState('');
|
6 |
|
7 |
const handleSubmit = (e) => {
|
8 |
e.preventDefault();
|
9 |
if (!ticker.trim() || isLoading) return;
|
10 |
+
onAnalyze(ticker);
|
11 |
};
|
12 |
|
13 |
return (
|
frontend/src/components/JobStatusCard.jsx
CHANGED
@@ -3,7 +3,6 @@ import { LoaderCircle, CheckCircle2, XCircle, FileClock, Database, Search, Bot }
|
|
3 |
|
4 |
function JobStatusCard({ job }) {
|
5 |
const getStatusInfo = (status) => {
|
6 |
-
// This map now perfectly matches the statuses set in your main_task.py
|
7 |
const statusMap = {
|
8 |
'PENDING': {
|
9 |
icon: <FileClock className="w-8 h-8 text-yellow-400" />,
|
|
|
3 |
|
4 |
function JobStatusCard({ job }) {
|
5 |
const getStatusInfo = (status) => {
|
|
|
6 |
const statusMap = {
|
7 |
'PENDING': {
|
8 |
icon: <FileClock className="w-8 h-8 text-yellow-400" />,
|
frontend/src/components/LoadingSkeleton.jsx
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
import React from 'react';
|
2 |
|
3 |
-
// A simple helper for the pulsing effect
|
4 |
const SkeletonBlock = ({ className }) => (
|
5 |
<div className={`bg-gray-700 rounded-md animate-pulse ${className}`} />
|
6 |
);
|
|
|
1 |
import React from 'react';
|
2 |
|
|
|
3 |
const SkeletonBlock = ({ className }) => (
|
4 |
<div className={`bg-gray-700 rounded-md animate-pulse ${className}`} />
|
5 |
);
|
frontend/src/services/api.js
CHANGED
@@ -1,28 +1,3 @@
|
|
1 |
-
// import axios from 'axios';
|
2 |
-
|
3 |
-
// const apiClient = axios.create({
|
4 |
-
// baseURL: 'http://localhost:8000',
|
5 |
-
// headers: {
|
6 |
-
// 'Content-Type': 'application/json',
|
7 |
-
// },
|
8 |
-
// });
|
9 |
-
|
10 |
-
// export const createJob = (ticker) => {
|
11 |
-
// return apiClient.post('/jobs', { ticker });
|
12 |
-
// };
|
13 |
-
|
14 |
-
// export const getJob = (jobId) => {
|
15 |
-
// return apiClient.get(`/jobs/${jobId}`);
|
16 |
-
// };
|
17 |
-
|
18 |
-
|
19 |
-
// export const getJobsHistory = () => {
|
20 |
-
// return apiClient.get('/jobs');
|
21 |
-
// };
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
import axios from 'axios';
|
27 |
|
28 |
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import axios from 'axios';
|
2 |
|
3 |
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
|
tmp_down.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
from transformers import pipeline
|
2 |
|
3 |
print("Downloading NEW model 'ProsusAI/finbert'...")
|
4 |
-
# Using the pipeline API is the easiest way to download all necessary files
|
5 |
classifier = pipeline('text-classification', model='ProsusAI/finbert')
|
6 |
print("Download complete!")
|
|
|
1 |
from transformers import pipeline
|
2 |
|
3 |
print("Downloading NEW model 'ProsusAI/finbert'...")
|
|
|
4 |
classifier = pipeline('text-classification', model='ProsusAI/finbert')
|
5 |
print("Download complete!")
|