尚未收集仓库信息。
尚未检测到更新。GitHub 发布版本后会显示在这里。
制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗I was traveling in Taiwan and ran into a problem I couldn't shake: I'd open Google Maps, search for food nearby, and the top results were clearly tourist traps — high ratings, thousands of reviews, packed with visitors, but locals I talked to had never heard of them.
The star rating wasn't measuring quality. It was measuring tourist attention volume.
I came back and noticed the same problem in NYC. One of the most reviewed cities in the world, but the rankings are polluted by one-time visitors who have no stake in whether a neighborhood restaurant actually holds up. The places real New Yorkers return to every week were buried under spots that had learned to optimize for foot traffic, not food.
That's what Locals is built to fix.
Google Maps has enormous amounts of data. The problem isn't data quality — it's that all reviewers are treated equally.
A tourist visiting New York once and a local who's eaten out 200 times in the city carry completely different signal value. If you know who is leaving the review, you can re-weight the ratings to surface what locals actually think.
1. Score every reviewer
Each reviewer gets a localness score from 0 to 1, built from three signals:
2. Re-weight the ratings
Using those scores, every restaurant gets two separate ratings: a local-weighted rating and a tourist-weighted rating. The gap between them is one of the strongest signals of whether a place is a neighborhood staple or riding tourist hype.
3. Run a classifier
I hand-labeled 205 restaurants as "local-approved" or not to create training data, then trained a Random Forest on 20 features — review statistics, NLP signals from review text (quality language, local vs. tourist patterns), price range, and rating distribution.
It's graded only on restaurants it didn't train on (5-fold cross-validation, repeated 10 times with different shuffles). It ranks a local-approved place above one that isn't about 70% of the time (AUC ~0.70), against 60% for sorting by Google stars.
The model outputs a confidence score for each restaurant. That score drives the final ranking.
4. Surface it cleanly
The app lets you filter by neighborhood, cuisine, price range, and whether a place is open now. Restaurant pages show the local take in plain language — no jargon, no bar charts, just: "locals rate it 4.4 vs. tourists' 3.8 — regulars love it more than visitors do."
| Layer | Technology |
|---|---|
| Frontend | Next.js 16, TypeScript, Tailwind CSS |
| Backend | FastAPI, Python |
| Database | Supabase (PostgreSQL) |
| Auth | Supabase Auth (Google OAuth) |
| ML | scikit-learn (Random Forest), pandas, numpy |
| Data collection | Apify (Google Maps scraper) |
| Deployment | Vercel (frontend), Railway (backend) |
Dataset: 78,000+ reviews · 2,100+ restaurants scraped · 384 on the live list · 205 hand-labeled training examples · 20 classifier features
Backend
pip install -r requirements.txt
python3 -m uvicorn backend.main:app --port 8000
Frontend
cd frontend
npm install
npm run dev
App runs at http://localhost:3000.
Create frontend/.env.local:
NEXT_PUBLIC_API_URL=http://localhost:8000
AUTH_SECRET=<your-secret>
AUTH_GOOGLE_ID=<your-google-client-id>
AUTH_GOOGLE_SECRET=<your-google-client-secret>
NEXT_PUBLIC_SUPABASE_URL=<your-supabase-url>
SUPABASE_SERVICE_ROLE_KEY=<your-supabase-service-role-key>
locals/
├── backend/
│ ├── main.py # FastAPI app — recommendations, filters, open-now logic
│ └── data.csv # Enriched restaurant dataset (ML scores + metadata)
├── frontend/
│ ├── app/
│ │ ├── recommendations/ # Main discovery page with filters
│ │ ├── restaurant/[id]/ # Restaurant detail page
│ │ ├── favorites/ # Saved restaurants (per-user, Supabase-backed)
│ │ └── api/ # Auth + saved/visited API routes
│ └── components/ # RestaurantCard and shared UI
├── data/
│ ├── reviews.csv # Raw reviews (48k+)
│ └── restaurants.csv # Raw restaurant metadata
├── pipeline.py # ML training pipeline (reviewer scoring → classifier)
├── enrich_data.py # Enrichment from raw Apify output
└── scrape_google_maps.py # Apify scraper config