A NYC Restaurant Week website that doesn't suck.
Tired of fighting the official NYC Restaurant Week website? So were we.
BiteWeek is a lightning-fast, beautifully designed alternative that makes discovering Restaurant Week deals actually enjoyable. Search by cuisine, neighborhood, price—or just ask AI what you're in the mood for.
Filter restaurants by cuisine, neighborhood, price tier ($30 / $45 / $60), meal period, Google rating, and review count—all with intuitive controls for precise filtering.
See real Google ratings and review counts for each restaurant. Filter by minimum rating (4.5+, 4.0+, 3.5+) or minimum number of reviews to find proven favorites.
StreetEasy-style neighborhood selector with boroughs, areas, and individual neighborhoods. Select "Upper Manhattan" to automatically include UES, UWS, Harlem, and more.
Results are sorted by relevance by default, factoring in:
- Menu availability — restaurants with menus are prioritized
- Reservation links — Resy/OpenTable integration
- Popularity — Google review count (capped to avoid outlier dominance)
- Rating — Higher-rated restaurants bubble up
Ask questions in plain English:
"pasta in lower manhattan" "cheap sushi in williamsburg" "vegetarian dinner in brooklyn"
BiteWeek uses LLM tool calling (Groq primary, OpenAI fallback) to extract structured filters from your query, then executes precise SQL + text search on menu content.
We scrape and index actual menu content from each restaurant. Search for specific dishes like "lamb" or "risotto" and find restaurants that actually serve them.
One click takes you directly to each restaurant's booking page. No more hunting for links.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ Next.js UI │────▶│ FastAPI │────▶│ PostgreSQL │
│ │ │ │ │ (Supabase) │
└─────────────────┘ └────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Groq LLM │
│ llama-3.3-70b │
│ │
│ ↓ fallback ↓ │
│ │
│ OpenAI │
│ gpt-4o-mini │
└─────────────────┘
- User query → Groq LLM (or OpenAI fallback) with tool schema + available filter values
- LLM extracts structured filters:
{near: "Tribeca", menu_search: ["pasta", "penne", ...]} - Backend expands
nearto nearby neighborhoods using distance calculation - SQL filtering on structured fields + text search on menu content
- LLM summarizes results for the user
- Return restaurants with explanation
| Layer | Technology |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS |
| Backend | FastAPI, Python 3.13+, Pydantic |
| Database | PostgreSQL (Supabase) |
| AI | Groq API (primary), OpenAI (fallback) |
| Scraping | Playwright, BeautifulSoup, PyMuPDF |
| Package Manager | uv |
- Python 3.13+
- Node.js 18+
- PostgreSQL 17+ (or Supabase)
- uv
- Groq API key (free at console.groq.com)
git clone https://github.com/yourusername/biteweek.git
cd biteweek
# Install Python dependencies
uv sync
# Install frontend dependencies
cd frontend && npm install && cd ..# Option A: Local PostgreSQL
brew install postgresql@17
brew services start postgresql@17
createdb biteweek
uv run migrate
# Option B: Supabase (recommended for production)
# Create a project at supabase.com and get your DATABASE_URL# .env
DATABASE_URL=postgresql://localhost/biteweek # or Supabase URL
GROQ_API_KEY=gsk_... # from console.groq.com (primary)
OPENAI_API_KEY=sk-... # optional fallback for AI search
GOOGLE_PLACES_API_KEY=... # optional, for Google ratings# Run the full automated pipeline
uv run pipeline configs/nyc-restaurant-week.yamlThe pipeline will automatically:
- Scrape restaurant listings
- Scrape menu content
- Fetch Google ratings (if
GOOGLE_PLACES_API_KEYis set) - Scrape reservation URLs from Resy/OpenTable
- Clean and format menus using LLM (if
OPENAI_API_KEYis set) - Build AI search cache
See PIPELINE.md for full pipeline documentation.
# Terminal 1: Start API server
uv run api
# Terminal 2: Start frontend
cd frontend && npm run devOpen http://localhost:3000 and start exploring.
# Database
uv run migrate # Run database migrations
# Data Pipeline
uv run pipeline configs/nyc-restaurant-week.yaml # Run full pipeline
uv run pipeline configs/nyc-restaurant-week.yaml --no-resume # Force restart
uv run pipeline configs/nyc-restaurant-week.yaml --stages menu_cleaning # Run specific stages
# API
uv run api # Start FastAPI server (port 8000)| Method | Endpoint | Description |
|---|---|---|
GET |
/restaurants |
List restaurants with filters |
GET |
/restaurants/{id} |
Get restaurant details |
GET |
/filters |
Get available filter options |
POST |
/ask |
Natural language search with AI |
# Single filter
GET /restaurants?cuisine=Italian
# Multiple values (OR logic)
GET /restaurants?cuisine=Italian&cuisine=French&price=30&price=45
# Neighborhood filter
GET /restaurants?neighborhood=Soho&neighborhood=Tribeca&neighborhood=West%20Village
# Rating filters
GET /restaurants?min_rating=4.0&min_reviews=100curl -X POST http://localhost:8000/ask \
-H "Content-Type: application/json" \
-d '{"query": "pasta in lower manhattan"}'Response:
{
"restaurants": [...],
"reasoning": "I found 10 restaurants in Lower Manhattan with pasta on their menus, including A Pasta Bar in Soho and several Italian spots in Tribeca."
}-- Restaurant data
restaurants (
id TEXT PRIMARY KEY,
name TEXT,
cuisine JSONB, -- ["Italian", "Mediterranean"]
borough TEXT,
neighborhood TEXT,
price_tier INTEGER, -- 30, 45, or 60
meals JSONB, -- ["lunch", "dinner"]
dietary JSONB, -- ["vegetarian", "gluten-free"]
menu_url TEXT,
reservation_url TEXT,
address TEXT,
description TEXT,
google_rating NUMERIC(2,1), -- 1.0 to 5.0
google_review_count INTEGER,
google_place_id TEXT
)
-- Raw menu content (for text search)
menu_items (
restaurant_id TEXT REFERENCES restaurants(id),
content TEXT
)biteweek/
├── biteweek/ # Python backend
│ ├── api.py # FastAPI application
│ ├── db.py # PostgreSQL operations
│ ├── google_places.py # Google Places API client
│ ├── migrate.py # Database migration runner
│ ├── models.py # Pydantic models
│ ├── nl_search.py # LLM tool calling + search
│ ├── pipeline/ # Modular pipeline framework
│ │ ├── core.py # Pipeline orchestrator
│ │ ├── stages.py # Base stage classes
│ │ ├── state.py # State tracking
│ │ └── config.py # YAML config loader
│ ├── stages/ # Pipeline stage implementations
│ │ ├── scraping.py # Restaurant & menu scraping
│ │ ├── enrichment.py # Ratings & reservations
│ │ ├── cleaning.py # LLM menu processing
│ │ └── caching.py # AI search cache
│ ├── sources/ # Event-specific adapters
│ │ ├── base.py # Base source interface
│ │ └── nyc_restaurant_week.py # NYC implementation
│ └── utils/ # Reusable utilities
│ ├── web.py # Playwright helpers
│ ├── pdf.py # PDF extraction
│ ├── text.py # Text cleaning
│ └── fuzzy.py # Name matching
├── frontend/ # Next.js frontend
│ └── src/
│ ├── app/ # Pages
│ ├── components/ # React components (FilterSidebar, NeighborhoodPicker, etc.)
│ └── lib/ # API client, neighborhood hierarchy
├── configs/ # Pipeline configurations
│ └── nyc-restaurant-week.yaml
├── scripts/ # Pipeline runner
│ └── run_pipeline.py
├── migrations/ # SQL migration files
│ └── 001_initial_schema.sql
├── PIPELINE.md # Pipeline documentation
└── pyproject.toml
# .env
DATABASE_URL=postgresql://localhost/biteweek # PostgreSQL connection
GROQ_API_KEY=gsk_... # Primary LLM for AI search
OPENAI_API_KEY=sk-... # Fallback LLM (optional)
GOOGLE_PLACES_API_KEY=... # Optional, for Google ratingsContributions welcome! Open a PR for bug fixes, features, or improvements.
# Backend development
uv run uvicorn biteweek.api:app --reload
# Frontend development
cd frontend && npm run devMIT
Stop fighting bad UX. Start discovering great food.
