🏆 Winner · TUM.ai E-Lab Hackathon (Munich) · Project A × Yellow × TUM.ai
AI-powered investor relationship graph for founders - find warm intro paths, not cold email lists.
FundSignal maps your existing founder network, detects investors within it, scores fit against your startup profile, and surfaces the shortest warm introduction path to each investor. Built as a hackathon MVP with a functional graph engine, BFS pathfinding, AI scoring, and a live Supabase backend mode.
FundSignal was built during the Project A × Yellow × TUM.ai E-Lab Hackathon in Munich, hosted by AI E-Lab by TUM.ai.
The event brought together builders for a 1-day startup hackathon focused on rapidly building, pitching, and stress-testing early-stage ideas.
The event was organized with Project A, Yellow, and TUM.ai E-Lab, with Lovable as sponsor.
Our team won the hackathon.
I drove the product and frontend direction for FundSignal, proposing the web-product architecture using Lovable and Supabase, designing the user experience and system flow, and building the website/frontend. I also contributed to the product identity, including the name and logo, and helped shape the AI/product workflow during the hackathon.
This was a team effort: the graph engine, backend, and AI implementation were built collaboratively with teammates. The description above reflects my role and does not imply sole ownership of the entire project.
Most founders approach fundraising backwards - they build a list of investors and then try to figure out how to reach them. FundSignal inverts this: it starts from your existing network, finds which investors are already reachable through people you know, and ranks them by the combination of fit and warmth.
The core loop:
- Ingest your network (LinkedIn Saved Search exports, CSV, CRM)
- Build a weighted relationship graph (founder → contacts → investors)
- Run BFS to find the shortest warm path to every investor
- Score each investor by sector fit, stage match, and warmth
Data Sources
│
├── LinkedIn Sales Navigator (Saved Search → weekly email alert)
├── Manual CSV upload (linkedin_saved_search_synthetic.csv)
├── Email contact API (simulated)
└── CRM import (simulated)
│
▼
Ingestion Layer
│
├── HTML email parser (parses LinkedIn alert notification HTML)
├── CSV transformer (normalizes column schema → snake_case)
└── Supabase upload (upsert via vc_id + synthetic_person_id unique key)
│
▼
Supabase (PostgreSQL)
│
└── public.connection_list
vc_name · vc_id · first_name · last_name · title · company
location · linkedin_url · synthetic_person_id · degree · created_at
│
▼
Analytics Engine (src/lib/supabaseAnalytics.ts)
│
├── buildAnalytics() - aggregates rows into graph-ready data structures
├── Overlap detection - persons appearing under multiple vc_ids
├── VC ranking by people count and overlap connector count
├── Degree distribution, top companies, top locations
└── Warm path construction (founder → person → vc)
│
▼
Graph Engine (src/lib/graph.ts)
│
├── findWarmPath() - BFS shortest path between any two nodes
├── computeWarmthScore() - edge strength × hop penalty
├── computeFitScore() - sector overlap, stage match, portfolio relevance
├── rankInvestors() - combined score = fit × 0.65 + warmth × 0.35
└── generateIntroEmail() - personalized forwardable intro request
│
▼
Frontend (React + TypeScript + Vite)
│
├── NetworkGraph - SVG force layout with radial positioning
├── InvestorList - ranked cards with hover-to-highlight BFS path
├── SupabaseMode - live data dashboard with tabbed analytics
└── JsonPreview - raw graph data export for downstream tooling
The graph is a weighted undirected network of nodes and edges. Node types: founder, contact, angel, vc, micro_fund, portfolio. Each edge carries a strength value (0–1) representing relationship quality - co-founder relationships score near 1.0, conference acquaintances around 0.6–0.7.
BFS pathfinding (findWarmPath) finds the shortest connection chain between the founder and any investor. Warmth score penalizes extra hops and rewards high-strength edges:
warmth = (100 − (hops − 1) × 15) × avgEdgeStrength
Investor fit score combines:
- Sector overlap (up to 40 points)
- Stage match (up to 25 points)
- Portfolio relevance (up to 15 points)
- Location match (up to 10 points)
- Ticket size fit (up to 10 points)
Final combined score: fit × 0.65 + warmth × 0.35
The connection_list table stores 2nd-degree LinkedIn connections for each VC in your network. The overlap detection query identifies people who appear in the connection lists of multiple VCs - these are the highest-signal founders to investigate for investment.
-- Overlap detection
SELECT
synthetic_person_id,
first_name,
last_name,
title,
company,
COUNT(DISTINCT vc_id) AS vc_overlap_count,
STRING_AGG(vc_name, ', ') AS connected_vcs
FROM public.connection_list
GROUP BY synthetic_person_id, first_name, last_name, title, company
HAVING COUNT(DISTINCT vc_id) > 1
ORDER BY vc_overlap_count DESC;The buildAnalytics() function (src/lib/supabaseAnalytics.ts) computes the full analytics object from raw rows in a single pass, producing VC nodes, person nodes, edges, overlap connectors, VC rankings, and warm paths - all without any additional database queries.
The intended production ingestion flow:
- Each VC in your network runs a LinkedIn Sales Navigator Saved Search filtered to their 2nd-degree connections
- LinkedIn sends a weekly alert email with new matches (5 preview cards per email)
- Your backend parses the HTML email (BeautifulSoup) to extract name, headline, company, LinkedIn URL
- Alternatively, the VC exports a full Lead List CSV from Sales Navigator (Advanced plan required)
- CSV is uploaded to Supabase via the frontend or a Python ingestion script
- Daily diff job compares new snapshot against previous, detects new connections, fires alerts on overlaps
For the hackathon demo, a synthetic dataset of 488 rows across 5 VCs with ~8% overlap is provided as linkedin_saved_search_synthetic.csv.
| Layer | Technology |
|---|---|
| Frontend framework | React 18 + TypeScript |
| Build tool | Vite 5 + SWC |
| Styling | Tailwind CSS v3 |
| Component library | shadcn/ui (Radix UI primitives) |
| Data fetching | TanStack React Query v5 |
| Routing | React Router v6 |
| Database | Supabase (PostgreSQL) |
| Supabase client | @supabase/supabase-js v2 |
| Charts | Recharts |
| Icons | Lucide React |
| Development platform | Lovable |
| Package manager | Bun / npm |
| Testing | Vitest + Testing Library |
| Linting | ESLint 9 + TypeScript ESLint |
fundsignal/
├── src/
│ ├── components/
│ │ ├── Architecture.tsx # Pipeline flow diagram
│ │ ├── Hero.tsx # Landing hero section
│ │ ├── IntroModal.tsx # Intro email composer
│ │ ├── InvestorList.tsx # Ranked investor cards
│ │ ├── JsonPreview.tsx # Raw graph data export
│ │ ├── Metrics.tsx # Live signal metrics
│ │ ├── NetworkGraph.tsx # SVG relationship graph
│ │ ├── NodeDetailsPanel.tsx # Node detail sidebar
│ │ ├── PipelineCards.tsx # Data source status cards
│ │ ├── StartupCard.tsx # Startup profile display
│ │ ├── TopNav.tsx # Navigation bar
│ │ └── ui/ # shadcn/ui primitives
│ │
│ ├── hooks/
│ │ └── useConnectionRows.ts # Supabase live data hook
│ │
│ ├── integrations/
│ │ └── supabase/
│ │ ├── client.ts # Supabase client singleton
│ │ └── types.ts # Auto-generated DB types
│ │
│ ├── lib/
│ │ ├── data.ts # Demo graph nodes + edges
│ │ ├── graph.ts # BFS, scoring, email generation
│ │ ├── supabaseAnalytics.ts # Analytics engine for live data
│ │ ├── types.ts # GraphNode, GraphEdge, types
│ │ └── utils.ts # cn() utility
│ │
│ └── pages/
│ ├── Index.tsx # Demo mode main page
│ ├── NotFound.tsx # 404 page
│ └── SupabaseMode.tsx # Live Supabase dashboard
│
├── supabase/
│ └── config.toml # Supabase project config
│
├── public/ # Static assets
├── index.html # App entry point
├── package.json
├── tailwind.config.ts
├── vite.config.ts
└── vitest.config.ts
CREATE TABLE public.connection_list (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
vc_name TEXT NOT NULL,
vc_id TEXT NOT NULL,
first_name TEXT,
last_name TEXT,
title TEXT,
company TEXT,
location TEXT,
linkedin_url TEXT,
synthetic_person_id TEXT NOT NULL,
degree INTEGER DEFAULT 2,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_vc_person UNIQUE (vc_id, synthetic_person_id)
);
CREATE INDEX idx_vc_id ON public.connection_list(vc_id);
CREATE INDEX idx_person_id ON public.connection_list(synthetic_person_id);
CREATE INDEX idx_created ON public.connection_list(created_at);Enable RLS and add policies:
-- Enable RLS
ALTER TABLE public.connection_list ENABLE ROW LEVEL SECURITY;
-- Allow public read (tighten for production)
CREATE POLICY "Allow public read"
ON public.connection_list FOR SELECT USING (true);
-- Allow insert (use service_role key from backend)
CREATE POLICY "Allow insert"
ON public.connection_list FOR INSERT WITH CHECK (true);Go to Table Editor → connection_list → Insert → Import data from CSV and upload linkedin_saved_search_synthetic.csv. All 488 rows will be imported. The unique constraint on (vc_id, synthetic_person_id) prevents duplicates on re-import.
VITE_SUPABASE_URL=https://your-project-id.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=your-anon-keyThe anon key is safe to expose in the frontend - it respects RLS policies. Use the service_role key only in server-side scripts (never commit it or expose it in the frontend).
# Install dependencies
npm install
# Start dev server
npm run dev
# → http://localhost:8080
# Run tests
npm test
# Build for production
npm run buildThe project is built on Lovable and deploys automatically on push. For manual deployment:
npm run build
# → dist/ folder ready for any static hostCompatible with Vercel, Netlify, Cloudflare Pages, or any static hosting. No server required - all logic runs client-side, with Supabase as the only external service.
linkedin_saved_search_synthetic.csv - 488 rows of fully synthetic LinkedIn Sales Navigator 2nd-degree connection data:
- 5 synthetic VC contacts (VC001–VC005)
- 435 unique synthetic people
- ~8% overlap (35 people appear in 2+ VC datasets)
- Fields:
vc_name,vc_id,first_name,last_name,title,company,location,linkedin_url,synthetic_person_id,degree - All names, companies, and URLs are fictional. No real personal data.
- Ready for direct Supabase CSV import
The following are simulated in the demo and represent the production v2 build:
- LinkedIn OAuth multi-user - each VC authorizes the app, connection diffs run automatically
- Browser extension - passively captures connection data as users browse LinkedIn
- Gmail API parser - ingests LinkedIn Saved Search weekly alert emails automatically
- Zapier integration - Sales Navigator new match → webhook → Supabase upsert
- Twitter/X signal layer - monitors VC engagement with founder accounts
- Weekly diff alerts - email notification when a new person appears across 2+ VC networks
- CRM export - push ranked investors and warm paths to HubSpot or Notion
Hackathon MVP - built for demonstration purposes. All demo data is synthetic.