Live app: ycsearch-production.up.railway.app
yc-search-tour.mp4
The full source for the tour (HyperFrames composition + script + storyboard + narration pipeline) lives in video/. See video/HANDOFF.md.
A full Next.js app for:
- ingesting YC company data from
yc-oss/api - scraping each company website with Crawl4AI
- storing metadata + scrape snapshots + embeddings in Postgres
- searching with both faceted keyword search and semantic search
- visual analytics by batch over time with optional stacked color-by tags/industries
- Next.js (App Router, TypeScript, Tailwind)
- PostgreSQL via
pg - Crawl4AI (Python runtime) for website scraping
- OpenAI embeddings (
text-embedding-3-small) for semantic ranking
Copy .env.example to .env (or let make setup do it) and set:
OPENAI_API_KEY=...
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/yc_search
# SYNC_TOKEN=your-shared-secret-for-api-sync
# SQLITE_IMPORT_PATH=./data/yc_search.sqlite
# SYNC_SCRAPE_LIMIT=25
# SYNC_EMBED_LIMIT=50
# CRAWL4AI_PYTHON_BIN=python3
# CRAWL4AI_PAGE_TIMEOUT_MS=35000Use uv + make for seamless local bootstrap:
make setupWhat make setup does:
- installs Node deps (
npm install) - creates
.venvviauv - installs Python deps from
requirements-crawl4ai.txt - runs
python -m crawl4ai.install - installs Playwright Chromium browser binaries
- creates
.envfrom.env.example(if missing) - ensures
.envhasCRAWL4AI_PYTHON_BIN=.venv/bin/python
If you prefer manual Python setup:
uv venv .venv
uv pip install --python .venv/bin/python -r requirements-crawl4ai.txt
.venv/bin/python -m crawl4ai.install
.venv/bin/python -m playwright install chromiumQuick verification:
make doctorInstall deps:
npm installStart app:
npm run devOpen http://localhost:3000.
Run full pipeline:
npm run sync:allOr run in steps:
npm run db:migrate
npm run sync:yc
npm run sync:scrape
npm run sync:embedRun the bounded incremental job used by Railway cron:
npm run sync:incrementalScrape all companies in one large pass (Crawl4AI):
npm run sync:scrape -- --limit=20000Or explicit full refresh flow:
npm run db:migrate
npm run sync:yc
npm run sync:scrape -- --limit=20000
npm run sync:embed -- --limit=20000Targeted sample run (random 2026 companies):
npm run test:crawl4ai:sample
# optional:
# npm run test:crawl4ai:sample -- --limit=2sync:ycupserts YC data and marks only changed/new companies for scrape/embed.sync:scrapescrapes only rows withneeds_scrape = 1and writes snapshots withsource='crawl4ai'.sync:embedembeds only rows withneeds_embed = 1and skips unchanged content hashes.
This keeps scraping and embedding one-time unless company data/content changes.
If you already have local SQLite data, import it into Postgres once:
npm run db:import:sqlite
# or:
# npm run db:import:sqlite -- --from=/absolute/path/to/yc_search.sqliteGET /api/facets-> available tags, industries, years, stages, regionsGET /api/search-> keyword + faceted searchGET /api/semantic-search-> semantic search over precomputed embeddingsPOST /api/chat-> chat QA over semantic company retrieval + Crawl4AI snapshot contextGET /api/analytics-> filtered batch chart data, optional stacked category seriesGET /api/graph-> 3D force-graph data (filter-scoped for dashboard, or?focusId=<id>for a company-centred neighborhood)POST /api/sync-> runs one bounded incremental sync (optional token auth viaAuthorization: Bearer <SYNC_TOKEN>)GET /api/sync/status-> latest sync run, backlog counts, and sync timestamps
- Result cards show a small company logo to the left of the name (when available).
- Clicking a tag or industry chip on a company card toggles that value in the filter sidebar.
- The
Analyticstab uses the current query/filters and draws bars by batch over time. - In
Analytics,Color bysupports:none(single bar per batch)tags(stacked bars by top tags +Other)industries(stacked bars by top industries +Other)
- Each company detail page includes an embedded 3D force-graph of the 40 most semantically similar companies, sharing the same renderer used by the dashboard's companion graph pane. The focus company is highlighted with a glowing halo.
- Each company detail page shows a Crawl4AI website snapshot section with:
- extracted description
- extracted URLs
- full markdown (expand/collapse)
- Search dashboard includes an Ask YC Chat panel:
- asks natural-language questions over semantic company retrieval
- uses YC metadata plus Crawl4AI snapshot content
- returns answer text with cited company links and extracted URLs/socials
- Semantic search requires
OPENAI_API_KEY. - Crawl4AI requires a working Python env where
crawl4aiis installed. - The app now expects
DATABASE_URLfor Postgres. better-sqlite3remains as a dev-only dependency for one-off imports.
This repo is configured for Railway via Dockerfile + railway.json.
Recommended Railway setup:
- Add a dedicated Railway Postgres service.
- Set env vars on the web service:
DATABASE_URL=${{Postgres.DATABASE_URL}}OPENAI_API_KEY=...CRAWL4AI_PAGE_TIMEOUT_MS=35000(optional)SYNC_TOKEN=...(optional, for manual API-triggered sync)
- Deploy the web service with boot command:
npm run db:migrate && npm run start
- If migrating existing local data, run the one-off import against the new Postgres DB:
npm run db:import:sqlite
- Add a second Railway service from the same repo for background sync:
- command:
npm run db:migrate && npm run sync:incremental - cron:
*/30 * * * *
- command:
- Set cron-worker env vars:
DATABASE_URL=${{Postgres.DATABASE_URL}}OPENAI_API_KEY=...SYNC_SCRAPE_LIMIT=25SYNC_EMBED_LIMIT=50
Observability:
- use Railway Observability for logs/CPU/RAM on
webandsync-worker - use
GET /api/sync/statusfor the latest run plus pending scrape/embed backlog