# Project export: SignalStackVC - Table 310

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: UC Berkeley AI Hackathon 2025
- Tagline: All the startup signals in one place. So you can stop hunting and start investing.
- Devpost: https://devpost.com/software/signalstackvc
- GitHub: https://github.com/sgtanvi/signalStackVC
- Video: https://www.youtube.com/embed/rKnD-y0qQ_k?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Tanvi (16 commits)

## Devpost submission (written by the team)

### Overview

Hi! I am at Table 310

### Inspiration

As builders and founders ourselves, we constantly saw VC analysts juggling 10+ tabs just to understand what a startup does. Signals were scattered across Crunchbase, GitHub, LinkedIn, Product Hunt, and company websites — but there was no unified way to surface a clear, investor-relevant profile. SignalStackVC was born from that pain. We wanted a tool that felt like having your own research analyst — but faster, smarter, and automatic.

### What it does

SignalStackVC aggregates fragmented data across the web and builds an intelligent, structured profile of any startup — in seconds. You input a company name, and SignalStackVC pulls in high-signal data (funding, team, traction, tech, etc.) from trusted sources like Crunchbase, LinkedIn, GitHub, and press coverage. It categorizes content using semantic embeddings, filters noise using confidence scoring, and generates a clean, JSON-based company profile with clickable sources.

### How we built it

🧠 Smart Query Engine: Combines rule-based and LLM-generated queries tailored to each startup. 🔍 Search Layer: Uses SerpAPI to fetch high-relevance Google search results per query. 🧭 Categorization Engine: Uses OpenAI embeddings + cosine similarity to classify each result into funding, tech, team, product, etc. 🔗 Async Web Scraper: Enriches high-confidence links with live page content using aiohttp. 🤖 Profile Generator: Uses GPT-4 to generate a structured profile from categorized data, with strict constraints to avoid hallucination. 🗂 Backend Infra: Modular, CLI-friendly Python backend with rate limiting, deduplication, and JSON export.

### Challenges we ran into

Entity disambiguation: Sometimes we fetched unrelated results for similarly named tokens or products (e.g. VLY crypto vs vly.ai). Result quality: Google results can be noisy; we had to build custom filtering, scoring, and fallback logic to keep profiles clean. OpenAI hallucinations: Without strict grounding, the model sometimes mixed up team members or made up features — so we tightened prompts and added source matching logic.

### Accomplishments we're proud of

Built a fully functioning backend pipeline in under 48 hours. Extracted clean profiles for real YC startups like vly.ai, including team info and product summaries with cited sources. Designed a confidence-weighted categorization system that balances rules + embeddings. Modularized the codebase so it’s production-ready and extendable.

### What we learned

Combining LLMs with traditional rule-based systems leads to surprisingly robust data pipelines. Query quality is everything — the better your search prompts, the better the downstream data. Entity resolution is hard — and crucial. Filtering and exact-match logic saved us from tons of noise.

### What's next

for SignalStackVC 🎯 Frontend: Launch a UI where VCs can search and watch startups, not just generate profiles from CLI. 📊 Watchlists & Alerts: Let users track startup momentum over time (team changes, funding updates). 💬 Founder Signals: Extract founder posts from Substack, Medium, and LinkedIn. 🧠 Semantic Search: Let users search for “early-stage devtools with high traction” and get ranked results. 🔐 User accounts + history so analysts can save, comment, and collaborate on startup profiles.

## README (from the GitHub repository)

## 🔄 SignalStackVC

**All the startup signals, in one place. So you can stop hunting and start investing.**

### 🧠 Tagline

> We aggregate fragmented startup data into one clean, searchable intelligence layer — built for VCs, analysts, and scouts who don’t have time to click through ten tabs per company.

---

## 🧩 What SignalStack Does

SignalStack pulls in live data from across the web to build a **single, investor-grade profile** for any startup — with zero fluff and 100% relevance.

### 🗃️ Sources We Aggregate:

* 🌐 **Startup Website** – extract mission, product, features
* 💸 **Crunchbase / AngelList** – funding history, team info
* 🚀 **Product Hunt** – traction signals from launch
* 💻 **GitHub** – repo activity, tech credibility
* 🧑‍💼 **LinkedIn** – team structure, growth hints
* ✍️ **Medium / Substack** – founder insight, thought leadership

---

### 🔍 What You See:

For every startup you track or paste in, SignalStack generates a unified profile with:

* ✅ **What they do** – clear, AI-generated summary
* 🧠 **Team background** – LinkedIn + Crunchbase rollup
* 📈 **Traction signals** – PH votes, GitHub stars/commits
* 💰 **Funding insights** – round info + any investor mentions
* 🎯 **Market fit** – inferred category, use case, wedge
* 📝 **Your notes & tags** – add context for your team
* 🔔 **Save / Watchlist** – for later follow-up or alerts

---

### 🎯 Use Cases

* 👨‍💼 VC Analysts tired of 20-tab sourcing
* 🔍 Scouts needing fast, accurate snapshots
* 🧪 Fund partners looking for thesis-aligned prospects
* 📊 FOFs tracking emerging markets across portfolios

---

### 🧪 MVP = Working Flow:

1. Paste startup URL
2. SignalStack scrapes and aggregates info
3. LLM cleans + summarizes into profile
4. Display clean result (JSON + styled view)
5. Save profile or add notes

## Detected evidence (automated analysis)

Indexed codebase: 28 recognized source files, 230 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (50 of 50)

```
.gitignore
backend/__init__.py
backend/.gitignore
backend/app/main.py
backend/app/summarizer.py
backend/data/exampleoutput.json
backend/data/startups/1vly.ai_profile.json
backend/data/startups/2vly_ai_profile.json
backend/data/startups/3vly_ai_profile.json
backend/data/startups/4vly_ai_profile.json
backend/data/startups/5vly_ai_profile.json
backend/data/startups/cluely_profile.json
backend/data/startups/corgi_profile.json
backend/data/startups/notion_profile.json
backend/data/startups/Prev_cluely_profile.json
backend/data/startups/stripe_profile.json
backend/data/startups/vly_ai_profile.json
backend/data/test_scraped/stripe_scraped_content.json
backend/process/__init__.py
backend/process/keyword_score.py
backend/process/relavencescorer.py
backend/README.MD
backend/requirements.txt
backend/scrap/__init__.py
backend/scrap/query_final.py
backend/scrap/scraper.py
backend/test_startup_intelligence.py
backend/testingscript/query.py
backend/testingscript/queryone.py
backend/testingscript/querytwo.py
backend/testingscript/serpai.py
data/startups/vly_ai_profile.json
frontend/.gitignore
frontend/components.json
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/README.md
frontend/src/App.css
frontend/src/App.tsx
frontend/src/index.css
frontend/src/lib/utils.ts
frontend/src/main.tsx
frontend/src/services/api.ts
frontend/src/vite-env.d.ts
frontend/tsconfig.app.json
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.ts
README.MD
```

### Dependencies

- backend/requirements.txt: fastapi@==0.110.2, openai@==1.90.0, pandas@==2.2.2, pydantic@==2.7.0, python-dotenv@==1.1.0, python-multipart@==0.0.9, serpapi@==0.1.5, uvicorn@==0.29.0
- frontend/package.json: @eslint/js@^9.25.0, @tailwindcss/vite@^4.1.10, @types/node@^24.0.3, @types/react@^19.1.2, @types/react-dom@^19.1.2, @vitejs/plugin-react@^4.4.1, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9.25.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.19, globals@^16.0.0, lucide-react@^0.522.0, react@^19.1.0, react-dom@^19.1.0, tailwind-merge@^3.3.1, tailwindcss@^4.1.10, tw-animate-css@^1.3.4, typescript@~5.8.3, typescript-eslint@^8.30.1, vite@^6.3.5

### Recent commits (newest first)

- impvoevef entiity disambiguation, categorized sources, and output structure for startup profiles
- Revert query.py to ba8140d version
- adding tsx
- auto focus and making it its own lil THING
- fix the freaking logo-> note to me in 3 hours
- frontend updating...
- checking the prev
- cluely :P
- cluely :P
- scrapper
- whoops
- testing script with in backend folder with 'python scrap/query.py --test'
- testing backend
- backend
- frontend stepup
- first commit

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

### backend/requirements.txt

```
fastapi==0.110.2
uvicorn==0.29.0
python-dotenv==1.1.0
python-multipart==0.0.9
pandas==2.2.2
pydantic==2.7.0
serpapi==0.1.5
openai==1.90.0

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@tailwindcss/vite": "^4.1.10",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.522.0",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "tailwind-merge": "^3.3.1",
    "tailwindcss": "^4.1.10"
  },
  "devDependencies": {
    "@eslint/js": "^9.25.0",
    "@types/node": "^24.0.3",
    "@types/react": "^19.1.2",
    "@types/react-dom": "^19.1.2",
    "@vitejs/plugin-react": "^4.4.1",
    "eslint": "^9.25.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^16.0.0",
    "tw-animate-css": "^1.3.4",
    "typescript": "~5.8.3",
    "typescript-eslint": "^8.30.1",
    "vite": "^6.3.5"
  }
}

```

### frontend/src/main.tsx

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

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### backend/app/main.py

```python
# keep in alphabetical order to keep it clean
from dotenv import load_dotenv
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware
import os
import shutil
import uvicorn

load_dotenv()

app = FastAPI()


origins = [
    '*'
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,  # Allow credentials (e.g., cookies, authorization headers)
    allow_methods=["*"],    # Specify allowed HTTP methods (or use wildcard "*")
    allow_headers=["*"],    # Specify allowed HTTP headers (or use wildcard "*")
)


@app.get("/")
async def api_entry():
    """
    Simple health-check endpoint.
    """
    return {"Welcome": "SignalStackVC API"}

# Endpoint to handle CSV upload
@app.post("/upload-csv/")
async def upload_csv(file: UploadFile = File(...)):
    return {"Function": "Function"}

def main():
    try:
        HOST = os.getenv("HOST")
        PORT = int(os.getenv("PORT"))
    except Exception:
        print(
            "Error: Please make sure you have set the HOST and PORT environment variables correctly."
        )
        exit(2)
    uvicorn.run(
        app,
        host=HOST,
        port=PORT,
        log_level="info",
    )


if __name__ == "__main__":
    main()

```

### frontend/src/App.tsx

```typescript
import React, { useState } from 'react';
import { Globe, Squirrel, TrendingUp, Search, Sparkle, Users, Filter, Tag, ExternalLink, ListCheck, Rocket, 
  Github, Linkedin, Star, Eye, Bell 
} from 'lucide-react';
import axios from 'axios';

// Define TypeScript interfaces for our data structures
interface StartupTraction {
  productHunt: {
    votes: number;
    rank: string;
  };
  github: {
    stars: number;
    commits: number;
  };
  linkedin: {
    employees: number;
    growth: string;
  };
}

interface StartupSignals {
  hot: boolean;
  funding: string;
  hiring: string;
  product: string;
}

interface ProfileMetadata {
  query_mode: string;
  enriched: boolean;
  timestamp: string;
}

interface Startup {
  id: number;
  name: string;
  url: string;
  description: string;
  category: string;
  stage: string;
  funding: string;
  team: string;
  founded: string;
  traction: StartupTraction;
  signals: StartupSignals;
  notes: string;
  tags: string[];
  confidence_scores?: Record<string, number>;
  category_counts?: Record<string, number>;
  raw_data?: any;
  profile_metadata?: ProfileMetadata;
}

interface SignalBadgeProps {
  type: string;
  text: string;
  hot?: boolean;
}

const SignalStackVC: React.FC = () => {
  // State for the search input
  const [searchUrl, setSearchUrl] = useState<string>('');
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [watchlist, setWatchlist] = useState<Startup[]>([]);
  const [activeTab, setActiveTab] = useState<string>('search');
  const [selectedStartup, setSelectedStartup] = useState<Startup | null>(null);
  
  // Create a separate handler for input changes
  const handleSearchInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSearchUrl(e.target.value);
  };

  const handleSearch = async () => {
    if (!searchUrl.trim()) return;
    
    setIsLoading(true);
    
    try {
      // Extract startup name from URL or use as is
      const startupName = extractStartupName(searchUrl);
      
      // Call your backend API
      const response = await axios.post('/api/analyze-startup', {
        startup_name: startupName,
        query_mode: 'hybrid',
        enrich_content: false
      });
      
      // Transform backend data to frontend format
      const startupData = transformProfileData(response.data);
      
      setSelectedStartup(startupData);
      setActiveTab('profile');
    } catch (error) {
      console.error('Error analyzing startup:', error);
      // Show error message to user
      alert('Failed to analyze startup. Please try again.');
    } finally {
      setIsLoading(false);
    }
  };

  const addToWatchlist = (startup: Startup) => {
    if (!watchlist.find(item => item.id === startup.id)) {
      setWatchlist([...watchlist, startup]);
    }
  };

  const SignalBadge: React.FC<SignalBadgeProps> = ({ type, text, hot = false }) => (
    <div className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
      hot 
        ? 'bg-orange-100 text-orange-800 border border-orange-200' 
        : 'bg-blue-50 text-blue-700 border border-blue-200'
    }`}>
      {hot && <TrendingUp className="w-3 h-3 mr-1" />}
      {text}
    </div>
  );

  const SearchTab: React.FC = () => (
    <div className="min-h-screen flex items-start pt-[20vh]">
      <div className="max-w-4xl mx-auto">
        <div className="text-center mb-12">
          <h1 className="text-4xl font-bold text-gray-900 mb-4">
            SignalStackVC
          </h1>
          <p className="text-xl text-gray-600 mb-8">
            All the startup signals, in one place. So you can stop hunting and start investing.
          </p>
          <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
            <div className="flex gap-4 mb-6">
              <div className="flex-1">
                <input
                  type="text"
                  placeholder="Paste startup URL or name..."
                  value={searchUrl}
                  onChange={handleSearchInputChange}
                  className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-900"
                />
              </div>
              <button
                onClick={handleSearch}
                disabled={isLoading}
                className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 flex items-center gap-2">
                {isLoading ? (
                  <div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
                ) : (
                  <Search className="w-5 h-5" />
                )}
                {isLoading ? 'Analyzing...' : 'Analyze'}
              </button>
            </div>
            
            <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm text-gray-600">
              <div className="flex items-center gap-2">
                <Sparkle className="w-4 h-4 text-blue-600" />
                Analyze Startup
              </div>
              <div className="flex items-center gap-2">
                <Users className="w-4 h-4 text-blue-600" />
                Analyze Batch
              </div>
              <div className="flex items-center gap-2">
                <ListCheck className="w-4 h-4 text-blue-600" />
                List Startups
              </div>
              <div className="flex items-center gap-2">
                <Squirrel className="w-4 h-4 text-blue-600" />
                Get Startup
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );

  const ProfileTab: React.FC = () => {
    if (!selectedStartup) return <div>No startup selected</div>;
    
    // Add state for notes
    const [notes, setNotes] = useState<string>(selectedStartup.notes || "");
    const [isSaving, setIsSaving] = useState<boolean>(false);
    
    // Func
[truncated — 21808 more characters]
```

### backend/__init__.py

```python
# This file makes the directory a Python package
```

### frontend/vite.config.ts

```typescript
import path from "path"
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
})
```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!--  CHANGE THIS AFTER WE GET LOGO -->
    <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### frontend/eslint.config.js

```javascript
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
  { ignores: ['dist'] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },
  },
)

```

### backend/test_startup_intelligence.py

```python
#!/usr/bin/env python3
"""
Simple test script for startup intelligence gathering.
Run this to test the startup profile generation functionality.
"""

import sys
import os
sys.path.append(os.path.dirname(__file__))

# Add the parent directory to the path so we can import backend modules
sys.path.append(os.path.dirname(os.path.dirname(__file__)))

from backend.scrap.query_final import main

if __name__ == "__main__":
    # Test with the original startup to confirm structure
    startup_name = "vly ai"  # Back to original test case
    print(f"Testing startup intelligence gathering for: {startup_name}")
    
    # Run the main function
    profile = main(startup_name=startup_name, query_mode="hybrid", enrich_content=False)
    
    if profile:
        print(f"\nSuccessfully generated profile for {startup_name}")
        print(f"Summary: {profile.get('summary', 'No summary available')}")
        print(f"Product: {profile.get('product', 'No product info available')}")
        print(f"Team: {profile.get('team', 'No team info available')}")
        print(f"Funding: {profile.get('funding', 'No funding info available')}")
        print(f"Sources: {len(profile.get('sources', []))} real URLs extracted")
        print(f"Confidence scores: {profile.get('confidence_scores', {})}")
        print(f"Category counts: {profile.get('category_counts', {})}")
    else:
        print(" Failed to generate profile") 
```

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