# Project export: InvestoMommy

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: An AI driven web application that lowers barriers to stock investing for beginners, turning complex stock valuations, Monte Carlo simulations, and news sentiment analysis into digestible insights.
- Devpost: https://devpost.com/software/investomommy
- GitHub: https://github.com/jho1211/investomommy-calhacks.git
- Video: https://www.youtube.com/embed/mtFvr-JhoHE?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Jeff (24 commits), gpt-engineer-app[bot] (13 commits), Wehbe El Hadj Sidi (3 commits), Jina Yeom (1 commits)

## Devpost submission (written by the team)

### Overview

Our

### Inspiration

In an era where our community is hyper-fixated on building the next flashy AI or humanoid robot, we wanted to refocus our energy into building something that addresses a need that affects another community that hold great importance to us: our families. Everyone on our team comes from an immigrant family, parents who traded a lifetime of hours and physical labor for income, working tirelessly to give us the opportunity to chase our dreams in achieving the "sexy" tech ventures everyone is focused on today. But in doing so, many never had the time, language skills, or resources to learn financial literacy to invest and build passive income. As children of immigrants, watching our parents struggle through job insecurity, rising living costs, and uncertainty in navigating the investment world inspired us to create InvestoMommy, a tool that lowers the barriers to investing, making the money that our families have worked so hard for, make money back for them. What Our Service Offers InvestoMommy brings professional-grade investment analysis to everyday users through five powerful features: Absolute Valuation (DCF): Estimate a company’s true worth by forecasting future cash flows and discounting them to present value. Relative Valuation (Price Multiples): Compare a stock’s metrics (eg P/E or EV/EBITDA) to industry peers to spot over- or undervaluation. Monte Carlo Simulation: Model thousands of possible market scenarios to visualize risk, volatility, and probability-based price outcomes. News Sentiment Analysis: Leverage FinBERT NLP to interpret financial headlines and measure how market sentiment affects a company’s outlook. Deep Dive Equity Research: Access a comprehensive research dashboard that consolidates valuation models, company fundamentals, and sentiment trends—all in one place. TAM/SAM/SOM Disclaimer: These figures are rough estimates that we plan to refine further with more accurate market research and user data. Total Addressable Market (TAM) The global fintech market is worth around $340B and projected to reach $930B by 2030. Out of that, financial analytics and education tools make up about $50B in opportunity. There are over 500 million active retail investors worldwide who look for platforms that make market data easier to understand. TAM = ~$50B Serviceable Available Market (SAM) Our first focus is North America. North America makes up about 38% of the fintech analytics space, or ~$19B. Within that about 90 million people fall into the group we can realistically reach who are trying to understand finance in a simple, interactive way. SAM = ~$5-6B Serviceable Obtainable Market (SOM) After talking with professionals from J.P. Morgan, Goldman Sachs, Baird, Morgan Stanley, Bank of America, Cresset and others we built a realistic market capture goal: If we reach 1% of the North American segment (~900,000 users) at $5/month that’s $54M a year. A smaller achievable goal is 0.3% of that group (~270,000 users) which equals about $16M in annual revenue. These numbers reflect what’s possible in the next 3–5 years with steady growth and user adoption. SOM = $16M - $54M (3-5 years) How We Built It Research Consulted equity researchers and executives at JP Morgan, Bank of America, and Morgan Stanley to understand strategies used for valuing businesses and stock prices. Referred to industry-trusted resources to read financial documents (eg Investopedia, Yahoo Finance, and Warren Buffet). Researched formulas, algorithms, and other quantitative/qualitative strategies used to assess stock value. Front End Lovable to create a skeleton of the front end. React, TypeScript, TailwindCSS, and HTML/CSS to further customize the front end to accommodate features we wanted to offer Supabase Auth for user authentication and generating UUIDs Back End Python FastAPI that coded the following features: Relative Valuation - Financial metrics pulled via FMP API, custom back end calculations for price multiples/ratios, PostgreSQL to save analyzed data for previously run stocks. Absolute Valuation - Python algorithm for DCF model written with Claude and financial metrics from Yahoo Finance and FMP API. Monte Carlo Simulation - One of our team members, a Junior Analyst at Montano Investment Fund, brought hands-on experience in designing Monte Carlo simulations, expertise that helped us model stock volatility and forecast valuation outcomes. News Sentiment Analysis - Extracting real-time headlines from the Finnhub API and running through FinBERT NLP model and Claude to quantify market sentiment and visualize how news impacts stock performance. Using the following technologies: Lava Payments - Used API to have a unified way of querying LLMs with a single source of credits Supabase Database - PostgreSQL database for storing stock analyses data and user watchlists Challenges We Ran Into Some challenges we ran into are the following: Accomplishing specific accuracy with the DCF model for valuing intrinsic value of stocks Navigating sentiment analyses of news articles related to stocks, and deciding which qualitative data to extract What's Next for Us To honor one of our core values at InvestoMommy, accessibility, we plan on implementing these additional features to make our product more accessible for people of different demographics and skill sets. Translating the application UI, explanation of finance terms, LLM-sentiment analysis of news headlines, and explanation of calculations to the next two most common languages spoken in the US, according to the United States Census Bureau: Spanish and Chinese. Liaise with equity researchers and investment bankers to fine-tune the DCF model to improve accuracy of intrinsic valuations of companies. Integrate brokerage app connections, allowing users to link their investment accounts directly to InvestoMommy so users can analyze potential stocks and make informed investment decisions with the ease of a few clicks. Broaden the news sentiment analysis to include news related to the stock's sector and any general news that might affect the stock performance. We envision a future where InvestoMommy can empower beginner-investors, like our immigrant families, to learn about stock investing and eventually build sustainable wealth.

## README (from the GitHub repository)

## Our Inspiration
In an era where our community is hyper-fixated on building the next flashy AI or humanoid robot, we wanted to refocus our energy into building something that addresses a need that affects another community that hold great importance to us: our families.

Everyone on our team comes from an immigrant family, parents who traded a lifetime of hours and physical labor for income, working tirelessly to give us the opportunity to chase our dreams in achieving the "sexy" tech ventures everyone is focused on today. But in doing so, many never had the time, language skills, or resources to learn financial literacy to invest and build passive income. 

As children of immigrants, watching our parents struggle through job insecurity, rising living costs, and uncertainty in navigating the investment world inspired us to create InvestoMommy, a tool that lowers the barriers to investing, making the money that our families have worked so hard for, make money back for them. 

## What Our Service Offers
InvestoMommy brings professional-grade investment analysis to everyday users through five powerful features:
1. **Absolute Valuation (DCF)**: Estimate a company’s true worth by forecasting future cash flows and discounting them to present value.
2. **Relative Valuation (Price Multiples)**: Compare a stock’s metrics (eg P/E or EV/EBITDA) to industry peers to spot over- or undervaluation.
3. **Monte Carlo Simulation**: Model thousands of possible market scenarios to visualize risk, volatility, and probability-based price outcomes.
4. **News Sentiment Analysis**: Leverage FinBERT NLP to interpret financial headlines and measure how market sentiment affects a company’s outlook.
5. **Deep Dive Equity Research**: Access a comprehensive research dashboard that consolidates valuation models, company fundamentals, and sentiment trends—all in one place.

## TAM/SAM/SOM<br>
*Disclaimer: These figures are rough estimates that we plan to refine further with more accurate market research and user data.*
### Total Addressable Market (TAM)
- The global fintech market is worth around $340B and projected to reach $930B by 2030.
- Out of that, financial analytics and education tools make up about $50B in opportunity.
- There are over 500 million active retail investors worldwide who look for platforms that make market data easier to understand.<br>
**TAM = ~$50B**

### Serviceable Available Market (SAM)
Our first focus is North America. 
- North America makes up about **38%** of the fintech analytics space, or **~$19B**.
- Within that about **90 million people** fall into the group we can realistically reach who are trying to understand finance in a simple, interactive way. <br>
**SAM = ~$5-6B**

### Serviceable Obtainable Market (SOM) 
After talking with professionals from J.P. Morgan, Goldman Sachs, Baird, Morgan Stanley, Bank of America, Cresset and others we built a realistic market capture goal: 
- If we reach 1% of the North American segment (~900,000 users) at $5/month that’s $54M a year.
- A smaller achievable goal is 0.3% of that group (~270,000 users) which equals about $16M in annual revenue.
- These numbers reflect what’s possible in the next 3–5 years with steady growth and user adoption.<br>
**SOM = $16M - $54M (3-5 years)**

## How We Built It
**Research** <br>
1. Consulted equity researchers and executives at JP Morgan, Bank of America, and Morgan Stanley to understand strategies used for valuing businesses and stock prices.
2. Referred to industry-trusted resources to read financial documents (eg Investopedia, Yahoo Finance, and Warren Buffet).
3. Researched formulas, algorithms, and other quantitative/qualitative strategies used to assess stock value.

**Front End**<br>
- **Lovable** to create a skeleton of the front end.
- **React, TypeScript, TailwindCSS, and HTML/CSS** to further customize the front end to accommodate features we wanted to offer 
- **Supabase PostgreSQL** databases to save user authentication data, stock analyses data, and stock watchlists

**Back End**<br>
Python FastAPI that coded the following features: 
- **Relative Valuation** - Financial metrics pulled via FMP API, custom back end calculations for price multiples/ratios, PostgreSQL to save analyzed data for previously run stocks.
- **Absolute Valuation** - Python algorithm for DCF model written with Claude and financial metrics from Yahoo Finance and FMP API.
- **Monte Carlo Simulation** - One of our team members, a Junior Analyst at Montano Investment Fund, brought hands-on experience in designing Monte Carlo simulations, expertise that helped us model stock volatility and forecast valuation outcomes.
- **News Sentiment Analysis** - Extracting real-time headlines from the Finnhub API and running through FinBERT NLP model and Claude to quantify market sentiment and visualize how news impacts stock performance.

## Challenges We Ran Into
Some challenges we ran into are the following: 
- Accomplishing specific accuracy with the DCF model for valuing intrinsic value of stocks
- Navigating sentiment analyses of news articles related to stocks, and deciding which qualitative data to extract

## What's Next for Us
To honor one of our core values at InvestoMommy, **accessibility**, we plan on implementing these additional features to make our product more accessible for people of different demographics and skill sets. 
1. Translating the application UI, explanation of finance terms, LLM-sentiment analysis of news headlines, and explanation of calculations to the next two most common languages spoken in the US, according to the [United States Census Bureau](https://www.census.gov/library/stories/2022/12/languages-we-speak-in-united-states.html): Spanish and Chinese.
2. Liaise with equity researchers and investment bankers to fine-tune the DCF model to improve accuracy of intrinsic valuations of companies.
3. Integrate brokerage app connections, allowing users to link their investment accounts directly to InvestoMommy so users can analyze potential stocks and make informed investment decisions with the ease of a few clicks.   

We envision a future where InvestoMommy can empower beginner-investors, like our immigrant families, to learn about stock investing and eventually build sustainable wealth.


## Detected evidence (automated analysis)

Indexed codebase: 94 recognized source files, 340 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- Hugging Face (technology) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- PyTorch (technology) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (110 of 110)

```
.gitignore
backend/__init__.py
backend/auth.py
backend/config.py
backend/dcf_calc.py
backend/dcf.py
backend/model.py
backend/query.py
backend/README.md
backend/requirements.txt
backend/server.py
frontend/.gitignore
frontend/bun.lockb
frontend/components.json
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/public/robots.txt
frontend/README.md
frontend/src/App.css
frontend/src/App.tsx
frontend/src/components/AddToWatchlistButton.tsx
frontend/src/components/DCF.tsx
frontend/src/components/MonteCarloChart.tsx
frontend/src/components/Navigation.tsx
frontend/src/components/NewsAnalysis.tsx
frontend/src/components/SensitivityHeatMap.tsx
frontend/src/components/ui/accordion.tsx
frontend/src/components/ui/alert-dialog.tsx
frontend/src/components/ui/alert.tsx
frontend/src/components/ui/aspect-ratio.tsx
frontend/src/components/ui/avatar.tsx
frontend/src/components/ui/badge.tsx
frontend/src/components/ui/breadcrumb.tsx
frontend/src/components/ui/button.tsx
frontend/src/components/ui/calendar.tsx
frontend/src/components/ui/card.tsx
frontend/src/components/ui/carousel.tsx
frontend/src/components/ui/chart.tsx
frontend/src/components/ui/checkbox.tsx
frontend/src/components/ui/collapsible.tsx
frontend/src/components/ui/command.tsx
frontend/src/components/ui/context-menu.tsx
frontend/src/components/ui/dialog.tsx
frontend/src/components/ui/drawer.tsx
frontend/src/components/ui/dropdown-menu.tsx
frontend/src/components/ui/form.tsx
frontend/src/components/ui/hover-card.tsx
frontend/src/components/ui/input-otp.tsx
frontend/src/components/ui/input.tsx
frontend/src/components/ui/label.tsx
frontend/src/components/ui/menubar.tsx
frontend/src/components/ui/navigation-menu.tsx
frontend/src/components/ui/pagination.tsx
frontend/src/components/ui/popover.tsx
frontend/src/components/ui/progress.tsx
frontend/src/components/ui/radio-group.tsx
frontend/src/components/ui/resizable.tsx
frontend/src/components/ui/scroll-area.tsx
frontend/src/components/ui/select.tsx
frontend/src/components/ui/separator.tsx
frontend/src/components/ui/sheet.tsx
frontend/src/components/ui/sidebar.tsx
frontend/src/components/ui/skeleton.tsx
frontend/src/components/ui/slider.tsx
frontend/src/components/ui/sonner.tsx
frontend/src/components/ui/switch.tsx
frontend/src/components/ui/table.tsx
frontend/src/components/ui/tabs.tsx
frontend/src/components/ui/textarea.tsx
frontend/src/components/ui/toast.tsx
frontend/src/components/ui/toaster.tsx
frontend/src/components/ui/toggle-group.tsx
frontend/src/components/ui/toggle.tsx
frontend/src/components/ui/tooltip.tsx
frontend/src/components/ui/use-toast.ts
frontend/src/contexts/AuthContext.tsx
frontend/src/hooks/use-mobile.tsx
frontend/src/hooks/use-toast.ts
frontend/src/index.css
frontend/src/lib/api.ts
frontend/src/lib/supabase.ts
frontend/src/lib/utils.ts
frontend/src/main.tsx
frontend/src/pages/About.tsx
frontend/src/pages/Analyze.tsx
frontend/src/pages/Dashboard.tsx
frontend/src/pages/DeepResearch.tsx
frontend/src/pages/Disclaimer.tsx
frontend/src/pages/Home.tsx
frontend/src/pages/HowItWorks.tsx
frontend/src/pages/Login.tsx
frontend/src/pages/NotFound.tsx
frontend/src/pages/StockAnalysis.tsx
frontend/src/vite-env.d.ts
frontend/tailwind.config.ts
frontend/tsconfig.app.json
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.ts
package.json
README.md
sentiment_analysis/.gitignore
sentiment_analysis/data/sentiment_results.json
sentiment_analysis/data/sentiment_summary.json
sentiment_analysis/main.py
sentiment_analysis/prompts/sentiment_prompt.txt
sentiment_analysis/requirements.txt
sentiment_analysis/supabase_client.py
```

### Dependencies

- backend/requirements.txt: cachetools@== 6.2.1, fastapi@==0.120.0, matplotlib@>=3.0.0, numpy@>=1.26.0, pandas@==2.3.3, python-dotenv@==1.1.1, python-jose@== 3.5.0, python-multipart@==0.0.20, requests@>=2.31.0, supabase@==2.22.2, uvicorn@==0.38.0, yfinance@==0.2.66
- frontend/package.json: @eslint/js@^9.32.0, @hookform/resolvers@^3.10.0, @radix-ui/react-accordion@^1.2.11, @radix-ui/react-alert-dialog@^1.1.14, @radix-ui/react-aspect-ratio@^1.1.7, @radix-ui/react-avatar@^1.1.10, @radix-ui/react-checkbox@^1.3.2, @radix-ui/react-collapsible@^1.1.11, @radix-ui/react-context-menu@^2.2.15, @radix-ui/react-dialog@^1.1.14, @radix-ui/react-dropdown-menu@^2.1.15, @radix-ui/react-hover-card@^1.1.14, @radix-ui/react-label@^2.1.7, @radix-ui/react-menubar@^1.1.15, @radix-ui/react-navigation-menu@^1.2.13, @radix-ui/react-popover@^1.1.14, @radix-ui/react-progress@^1.1.7, @radix-ui/react-radio-group@^1.3.7, @radix-ui/react-scroll-area@^1.2.9, @radix-ui/react-select@^2.2.5, @radix-ui/react-separator@^1.1.7, @radix-ui/react-slider@^1.3.5, @radix-ui/react-slot@^1.2.3, @radix-ui/react-switch@^1.2.5, @radix-ui/react-tabs@^1.1.12, @radix-ui/react-toast@^1.2.14, @radix-ui/react-toggle@^1.1.9, @radix-ui/react-toggle-group@^1.1.10, @radix-ui/react-tooltip@^1.2.7, @supabase/supabase-js@^2.76.1, @tailwindcss/typography@^0.5.16, @tanstack/react-query@^5.83.0, @types/node@^22.16.5, @types/react@^18.3.23, @types/react-dom@^18.3.7, @vitejs/plugin-react-swc@^3.11.0, autoprefixer@^10.4.21, axios@^1.13.1, chart.js@^4.5.1, chartjs-chart-matrix@^3.0.0, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@^1.1.1, date-fns@^3.6.0, embla-carousel-react@^8.6.0, eslint@^9.32.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.20, globals@^15.15.0, input-otp@^1.4.2, lovable-tagger@^1.1.11, lucide-react@^0.462.0, next-themes@^0.3.0, postcss@^8.5.6, react@^18.3.1, react-chartjs-2@^5.3.0, react-day-picker@^8.10.1, react-dom@^18.3.1, react-hook-form@^7.61.1, react-resizable-panels@^2.1.9, react-router-dom@^6.30.1, recharts@^2.15.4, sonner@^1.7.4, tailwind-merge@^2.6.0, tailwindcss@^3.4.17, tailwindcss-animate@^1.0.7, typescript@^5.8.3, typescript-eslint@^8.38.0, vaul@^0.9.9, vite@^5.4.19, zod@^3.25.76
- package.json: chart.js@^4.5.1, chartjs-chart-matrix@^3.0.0, react-chartjs-2@^5.3.0
- sentiment_analysis/requirements.txt: certifi@==2025.10.5, charset-normalizer@==3.4.4, filelock@==3.20.0, fsspec@==2025.9.0, ftfy@==6.3.1, hf-xet@==1.2.0, huggingface-hub@==0.36.0, idna@==3.11, Jinja2@==3.1.6, MarkupSafe@==3.0.3, mpmath@==1.3.0, networkx@==3.5, numpy@==2.3.4, packaging@==25.0, python-dotenv@==1.1.1, PyYAML@==6.0.3, regex@==2025.10.23, requests@==2.32.5, safetensors@==0.6.2, setuptools@==80.9.0, sympy@==1.14.0, tokenizers@==0.22.1, torch@==2.9.0, tqdm@==4.67.1, transformers@==4.57.1, typing_extensions@==4.15.0, urllib3@==2.5.0

### Recent commits (newest first)

- Merge pull request #3 from jho1211/backend-auth
- Added endpoint to get a test token
- add auth to backend and frontend
- Merge pull request #2 from jho1211/production
- fix backend so that it properly serves all frontend routes
- change how the frontend is served. backend now serves static frontend files
- Merge pull request #1 from jho1211/production
- comment out generating research brief for production backend
- add CORS for frontend
- use production backend api url
- remove console log statements
- migrate DCF router to backend folder
- Remove unused folder
- Changed news headline sentiment to remove %
- Added sensitivity heat map for DCF
- Add comprehensive overview of InvestoMommy to READ.me
- fix dcf analysis on frontend
- Troubleshooting the DCF microservice
- Troubleshooting the DCF microservice
- get lava implemented instead of using anthropic client

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "dependencies": {
    "chart.js": "^4.5.1",
    "chartjs-chart-matrix": "^3.0.0",
    "react-chartjs-2": "^5.3.0"
  }
}

```

### backend/requirements.txt

```
# Core dependencies for the backend API
fastapi==0.120.0
uvicorn==0.38.0
python-dotenv==1.1.1
python-multipart==0.0.20
python-jose == 3.5.0
cachetools == 6.2.1

# Supabase client
supabase==2.22.2

# Data processing and analysis
pandas==2.3.3
numpy>=1.26.0
yfinance==0.2.66
matplotlib>=3.0.0

# HTTP client
requests>=2.31.0

```

### sentiment_analysis/requirements.txt

```
certifi==2025.10.5
charset-normalizer==3.4.4
filelock==3.20.0
fsspec==2025.9.0
hf-xet==1.2.0
huggingface-hub==0.36.0
idna==3.11
Jinja2==3.1.6
MarkupSafe==3.0.3
mpmath==1.3.0
networkx==3.5
numpy==2.3.4
packaging==25.0
python-dotenv==1.1.1
PyYAML==6.0.3
regex==2025.10.23
requests==2.32.5
safetensors==0.6.2
setuptools==80.9.0
sympy==1.14.0
tokenizers==0.22.1
torch==2.9.0
tqdm==4.67.1
transformers==4.57.1
typing_extensions==4.15.0
urllib3==2.5.0
ftfy==6.3.1
```

### frontend/package.json

```
{
  "name": "vite_react_shadcn_ts",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "build:dev": "vite build --mode development",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.10.0",
    "@radix-ui/react-accordion": "^1.2.11",
    "@radix-ui/react-alert-dialog": "^1.1.14",
    "@radix-ui/react-aspect-ratio": "^1.1.7",
    "@radix-ui/react-avatar": "^1.1.10",
    "@radix-ui/react-checkbox": "^1.3.2",
    "@radix-ui/react-collapsible": "^1.1.11",
    "@radix-ui/react-context-menu": "^2.2.15",
    "@radix-ui/react-dialog": "^1.1.14",
    "@radix-ui/react-dropdown-menu": "^2.1.15",
    "@radix-ui/react-hover-card": "^1.1.14",
    "@radix-ui/react-label": "^2.1.7",
    "@radix-ui/react-menubar": "^1.1.15",
    "@radix-ui/react-navigation-menu": "^1.2.13",
    "@radix-ui/react-popover": "^1.1.14",
    "@radix-ui/react-progress": "^1.1.7",
    "@radix-ui/react-radio-group": "^1.3.7",
    "@radix-ui/react-scroll-area": "^1.2.9",
    "@radix-ui/react-select": "^2.2.5",
    "@radix-ui/react-separator": "^1.1.7",
    "@radix-ui/react-slider": "^1.3.5",
    "@radix-ui/react-slot": "^1.2.3",
    "@radix-ui/react-switch": "^1.2.5",
    "@radix-ui/react-tabs": "^1.1.12",
    "@radix-ui/react-toast": "^1.2.14",
    "@radix-ui/react-toggle": "^1.1.9",
    "@radix-ui/react-toggle-group": "^1.1.10",
    "@radix-ui/react-tooltip": "^1.2.7",
    "@supabase/supabase-js": "^2.76.1",
    "@tanstack/react-query": "^5.83.0",
    "axios": "^1.13.1",
    "chart.js": "^4.5.1",
    "chartjs-chart-matrix": "^3.0.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "date-fns": "^3.6.0",
    "embla-carousel-react": "^8.6.0",
    "input-otp": "^1.4.2",
    "lucide-react": "^0.462.0",
    "next-themes": "^0.3.0",
    "react": "^18.3.1",
    "react-chartjs-2": "^5.3.0",
    "react-day-picker": "^8.10.1",
    "react-dom": "^18.3.1",
    "react-hook-form": "^7.61.1",
    "react-resizable-panels": "^2.1.9",
    "react-router-dom": "^6.30.1",
    "recharts": "^2.15.4",
    "sonner": "^1.7.4",
    "tailwind-merge": "^2.6.0",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^0.9.9",
    "zod": "^3.25.76"
  },
  "devDependencies": {
    "@eslint/js": "^9.32.0",
    "@tailwindcss/typography": "^0.5.16",
    "@types/node": "^22.16.5",
    "@types/react": "^18.3.23",
    "@types/react-dom": "^18.3.7",
    "@vitejs/plugin-react-swc": "^3.11.0",
    "autoprefixer": "^10.4.21",
    "eslint": "^9.32.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.20",
    "globals": "^15.15.0",
    "lovable-tagger": "^1.1.11",
    "postcss": "^8.5.6",
    "tailwindcss": "^3.4.17",
    "typescript": "^5.8.3",
    "typescript-eslint": "^8.38.0",
    "vite": "^5.4.19"
  }
}

```

### backend/server.py

```python
import logging
import traceback
from fastapi import FastAPI, Query, Request, Depends
from fastapi.responses import JSONResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer
import os
import mimetypes
from auth import verify_token

from model import (
    calculate_stock_multiples,
    run_monte_carlo,
    insert_user_ticker,
    generate_research_brief,
)
from query import (
    fetch_userlist,
    fetch_news_sentiment,
    fetch_overall_news_sentiment,
)
from dcf import router as dcf_router
from config import supabase

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("investomommy")

app = FastAPI(
    title="InvestoMommy API",
    description="Investment analysis API with Supabase authentication",
    version="1.0.0"
)

origins = [
    "https://investomommy-calhacks.onrender.com",
    "http://localhost:8000",
    "http://localhost:8080"
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,  # Adjust this in production
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/api/health")
def health():
    return {"ok": True}

@app.post("/api/auth/get-token")
def get_test_token(
        email: str = Query(..., description="User email"), 
        password: str = Query(..., description="User password")
    ):
    try:
        response = supabase.auth.sign_in_with_password({
            "email": email,
            "password": password
        })
        
        if response.session:
            return {
                "access_token": response.session.access_token,
                "token_type": "bearer",
                "expires_at": response.session.expires_at,
                "user_id": response.user.id if response.user else None,
            }
        else:
            return JSONResponse(
                status_code=401,
                content={"error": "Invalid credentials"}
            )
    except Exception as e:
        logger.exception("Test token generation failed")
        return JSONResponse(
            status_code=400,
            content={"error": str(e)}
        )

@app.exception_handler(Exception)
async def unhandled_exception_handler(request: Request, exc: Exception):
    logger.error("Unhandled error on %s %s\n%s",
                 request.method, request.url, traceback.format_exc())
    return JSONResponse(
        status_code=500,
        content={"error": "Internal server error"},
    )

# -------- Your existing endpoints --------
@app.get("/api/multiples")
def get_multiples_for_stock(
    ticker: str = Query(..., description="Stock ticker, e.g., AAPL"),
    user: dict = Depends(verify_token)
):
    return calculate_stock_multiples(ticker)

@app.get("/api/userlist")
def get_user_list(
    uid: str = Query(..., description="User ID"),
    user: dict = Depends(verify_token)
):
    return fetch_userlist(uid)

@app.post("/api/userlist")
def add_to_user_list(
    uid: str = Query(..., description="User ID"),
    ticker: str = Query(..., description="Stock ticker, e.g., AAPL"),
    user: dict = Depends(verify_token)
):
    try:
        return insert_user_ticker(uid, ticker)
    except Exception as e:
        logger.exception("Insert user ticker failed")
        return {"error": str(e)}

@app.get("/api/montecarlo")
async def montecarlo_endpoint(
    ticker: str = Query(..., description="Stock ticker, e.g., AAPL"),
    years_history: int = Query(5, ge=1, le=20),
    horizon_years: float = Query(1.0, gt=0),
    steps_per_year: int = Query(252, ge=50, le=2000),
    n_paths: int = Query(1000, ge=100, le=20000),
    user: dict = Depends(verify_token)
):
    try:
        result = run_monte_carlo(
            ticker=ticker,
            years_history=years_history,
            horizon_years=horizon_years,
            steps_per_year=steps_per_year,
            n_paths=n_paths,
        )
        return JSONResponse(content=result)
    except Exception as e:
        logger.exception("Monte Carlo failed")
        return JSONResponse(status_code=400, content={"error": str(e)})

@app.get("/api/research")
def research_endpoint(
    ticker: str = Query(..., description="Stock ticker, e.g., AAPL"),
    user: dict = Depends(verify_token)
):
    try:
        return generate_research_brief(ticker)
    except Exception as e:
        logger.exception("Research generation failed")
        return {"error": str(e)}

@app.get("/api/news-sentiment")
def news_sentiment_endpoint(
    ticker: str = Query(..., description="Stock ticker, e.g., AAPL"),
    user: dict = Depends(verify_token)
):
    return fetch_news_sentiment(ticker.upper())

@app.get("/api/overall-news-sentiment")
def overall_news_sentiment_endpoint(
    ticker: str = Query(..., description="Stock ticker, e.g., AAPL"),
    user: dict = Depends(verify_token)
):
    return fetch_overall_news_sentiment(ticker.upper())

app.include_router(dcf_router, prefix="/api", tags=["DCF"])

@app.get("/{full_path:path}")
async def serve_react_app(full_path: str):
    """
    Catch-all route to serve index.html for all non-API routes.
    This allows React Router to handle client-side routing.
    """
    file_path = os.path.join("build", full_path)
    
    if os.path.isfile(file_path):
        mime_type, _ = mimetypes.guess_type(file_path)
        return FileResponse(
            file_path,
            media_type=mime_type,
            headers={"Cache-Control": "public, max-age=3600"}
        )
    
    index_path = os.path.join("build", "index.html")
    if os.path.exists(index_path):
        return FileResponse(
            index_path,
            media_type="text/html",
            headers={"Cache-Control": "no-cache"}
        )
    
    return JSONResponse(status_code=404, content={"error": "Not found"})
```

### sentiment_analysis/main.py

```python
import os
import json
import requests 
import torch
from supabase import create_client
from datetime import date, timedelta
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch.nn.functional as F
from datetime import datetime
from dotenv import load_dotenv
import html
import ftfy

# --- Load API key ---
load_dotenv()
API_KEY = os.getenv("FINNHUB_API_KEY")
if not API_KEY:
    raise ValueError("⚠️ Please set FINNHUB_API_KEY in your .env file")

# --- FinBERT model setup ---
MODEL_NAME = "yiyanghkust/finbert-tone"
print("🔍 Loading FinBERT model...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME)
labels = ["positive", "neutral", "negative"]
print("✅ Model loaded successfully.")

# --- Supabase setup ---
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
if not SUPABASE_URL or not SUPABASE_KEY:
    raise ValueError("⚠️ Please set SUPABASE_URL and SUPABASE_KEY in your .env file")

supabase = create_client(SUPABASE_URL, SUPABASE_KEY)


# --- Predefined ticker list (you can expand this) ---
AVAILABLE_TICKERS = {
    "TSLA": "Tesla Inc.",
    "AAPL": "Apple Inc.",
    "MSFT": "Microsoft Corp.",
    "AMZN": "Amazon.com Inc.",
    "GOOGL": "Alphabet Inc.",
    "META": "Meta Platforms Inc.",
    "NVDA": "NVIDIA Corp.",
    "JPM": "JPMorgan Chase & Co.",
    "BAC": "Bank of America Corp.",
    "DIS": "Walt Disney Co."
}

# Clean up the news
def clean_text(text):
    if not text:
        return ""
    
    text = ftfy.fix_text(text)  # fixes mojibake and weird encodings

    # Replace curly quotes/dashes
    text = text.replace("’", "'").replace("“", '"').replace("”", '"')
    text = text.replace("–", "-").replace("—", "-")

    # Unescape HTML entities
    text = html.unescape(text)
    
    return text

# --- Function to fetch company news ---
def fetch_news(symbol, days=5, limit=10):
    today = date.today()
    from_date = (today - timedelta(days=days)).isoformat()
    to_date = today.isoformat()

    url = f"https://finnhub.io/api/v1/company-news?symbol={symbol}&from={from_date}&to={to_date}&token={API_KEY}"
    response = requests.get(url)

    if response.status_code != 200:
        print(f"❌ Error fetching news for {symbol}: {response.status_code}")
        return []
    
 
    data = response.json()


    news = [{
        "headline": clean_text(item.get("headline")),
        "datetime": item.get("datetime"),
        "url": item.get("url"),
        "summary": clean_text(item.get("summary"))
    } for item in data[:limit]]

    return news

# --- Function to analyze sentiment with FinBERT ---
def analyze_sentiment(news_list):
    results = []
    if not news_list:
        return results

    texts = [item["headline"] for item in news_list]
    inputs = tokenizer(texts, return_tensors="pt", truncation=True, padding=True)
    outputs = model(**inputs)
    probs = F.softmax(outputs.logits, dim=-1)

    for i, item in enumerate(news_list):
        sentiment = labels[probs[i].argmax()]
        confidence = round(probs[i].max().item(), 4)
        results.append({
            "headline": item["headline"],
            "datetime": item.get("datetime"),
            "sentiment": sentiment,
            "confidence": confidence,
            "url": item.get("url"),          # Add URL
            "summary": item.get("summary") or item.get("headline")[:200]  # Optional: short snippet
        })
    return results


# --- Uploading to Supabase (bulk insert version) ---
def upload_to_supabase(all_results, summary_results):
    # Convert datetime from timestamp to ISO string if needed
    for row in all_results:
        if isinstance(row.get("datetime"), int):
            row["datetime"] = datetime.fromtimestamp(row["datetime"]).isoformat()

    # Bulk insert all tickers into 'ticker' table
    if all_results:
        tickers_to_add = [{"ticker": symbol, "company_name": AVAILABLE_TICKERS[symbol]} for symbol in selected]
        supabase.table("ticker").upsert(tickers_to_add, on_conflict="ticker").execute()

        # Remove duplicates based on URL
        unique_results = {row['url']: row for row in all_results}.values()
        supabase.table("newssentiment").upsert(list(unique_results), on_conflict="url").execute()

    # Bulk insert all summaries into 'overallnewssentiment'
    if summary_results:
        supabase.table("overallnewssentiment").insert(summary_results).execute()


# --- MAIN ---
if __name__ == "__main__":
    print("\n📈 Available Tickers:")
    for i, (symbol, name) in enumerate(AVAILABLE_TICKERS.items(), 1):
        print(f"  {i}. {symbol} - {name}")

    # User selects tickers by typing numbers or symbols
    choice = input("Enter tickers (comma separated or numbers, e.g. 'AAPL, TSLA' or '1,3,5'): ").strip()


    selected = []
    parts = [c.strip().upper() for c in choice.split(",")]

    for p in parts:
        if p.isdigit() and 1 <= int(p) <= len(AVAILABLE_TICKERS):
            symbol = list(AVAILABLE_TICKERS.keys())[int(p)-1]
            selected.append(symbol)
        elif p in AVAILABLE_TICKERS:
            selected.append(p)

    if not selected:
        print("⚠️ No valid tickers selected. Exiting.")
        exit()

    print(f"\n📰 Fetching news for: {', '.join(selected)}\n")

    all_results = []
    summary_results = []
    os.makedirs("data", exist_ok=True)

    for ticker in selected:
        print(f"--- {ticker} ---")
        news_data = fetch_news(ticker, days=5, limit=10)
        print(f"Fetched {len(news_data)} news articles.")

        sentiment_results = analyze_sentiment(news_data)

        for r in sentiment_results:
            all_results.append({"ticker": ticker, **r})

        # --- Summary ---
        if sentiment_results:
            total = len(sentiment_results)
            counts = {"positive": 0, "neutral": 0, "negative": 0}
            for r in sentiment_results:
                counts[r["sentiment"]] += 1
         
[truncated — 887 more characters]
```

### frontend/src/main.tsx

```typescript
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";

createRoot(document.getElementById("root")!).render(<App />);

```

### frontend/src/App.tsx

```typescript
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./contexts/AuthContext";
import Home from "./pages/Home";
import Disclaimer from "./pages/Disclaimer";
import Login from "./pages/Login";
import Analyze from "./pages/Analyze";
import Dashboard from "./pages/Dashboard";
import About from "./pages/About";
import HowItWorks from "./pages/HowItWorks";
import StockAnalysis from "./pages/StockAnalysis";
import NotFound from "./pages/NotFound";
import DeepResearch from "./pages/DeepResearch";

const queryClient = new QueryClient();

const App = () => (
  <QueryClientProvider client={queryClient}>
    <AuthProvider>
      <TooltipProvider>
        <Toaster />
        <Sonner />
        <BrowserRouter>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/disclaimer" element={<Disclaimer />} />
            <Route path="/login" element={<Login />} />
            <Route path="/analyze" element={<Analyze />} />
            <Route path="/dashboard" element={<Dashboard />} />
            <Route path="/about" element={<About />} />
            <Route path="/how-it-works" element={<HowItWorks />} />
            <Route path="/dashboard/analysis/:ticker" element={<StockAnalysis />} />
            <Route path="*" element={<NotFound />} />
            <Route path="/research/:ticker" element={<DeepResearch />} />
          </Routes>
        </BrowserRouter>
      </TooltipProvider>
    </AuthProvider>
  </QueryClientProvider>
);

export default App;

```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

```

### sentiment_analysis/supabase_client.py

```python
# supabase_client.py
import os
from supabase import create_client
from dotenv import load_dotenv

load_dotenv()

SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")

supabase = create_client(SUPABASE_URL, SUPABASE_KEY)

```

[84 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]