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!")
         |