Not just a search tool. An autonomous agent that decides the fairest meeting point for everyone.
Qloo Agentic Hackathon 2026: group taste fairness · try the New York demo group
RevenueCat Shipaton 2026: demo video (1:48) · Devpost · macOS app + RevenueCat
Most location tools return results near you. MeetSpot calculates the geographic center of all participants and returns AI-ranked venues that minimize everyone's travel time.
| Traditional Tools | MeetSpot |
|---|---|
| Search near your location | Calculate fair center for all |
| Keyword-based ranking | AI-powered multi-factor scoring |
| Static results | Adaptive dual-mode routing |
| No reasoning | Explainable AI with chain-of-thought |
| When | What | Context |
|---|---|---|
| 2026-10 | Group taste fairness: each person lists what they like, Qloo scores the same venues once per person, and venues are ranked by the least-satisfied person (details, PR #92) | Qloo Agentic Hackathon 2026 |
| 2026-10 | macOS app + RevenueCat paywall: Electron desktop build with a server-verified meetspot_pro entitlement, running against the RevenueCat Test Store (details, PR #89, #90) |
RevenueCat Shipaton 2026 (results on 2026-10-20) |
| 2026-09 | Real commute-time fairness: when users set a max commute per person, candidate centers are checked against real travel times from Google Routes and rejected if anyone goes over (details, PR #84) | AI Builders Hackathon 2026 |
Picking a place for a group usually means somebody gets dragged somewhere they would never choose. MeetSpot already finds a fair location for everyone; since 2026-09-30 it also finds a fair place, using each person's own taste.
Each person lists a few things they love (artists, movies, shows, brands, books). MeetSpot resolves them to Qloo entities, has Qloo score the same set of nearby venues once per person, and ranks venues by the least-satisfied person (maximin over each person's percentile), not the group average. A venue that two people love and the third would hate is left out, and the page says who would have been unhappy and why.
flowchart LR
A[Addresses + tastes per person] --> B[Qloo /search: resolve each taste]
A --> C[Fair center + real commute check]
B --> D[Qloo heatmap per person: min per cell]
D --> C
C --> E[Qloo insights: 30 candidate venues near the center]
E --> F[Qloo insights per person on the same 30, with explainability]
F --> G[Percentile per person, rank by the worst-off person]
B --> H[Qloo analysis/compare: shared taste tags]
G --> I[Results page + MeetSpot agent tool group_taste_rank]
H --> I
How Qloo is used
| Qloo capability | What MeetSpot does with it |
|---|---|
/search |
Turns "Taylor Swift, Barbie" into entities and shows each person what their taste was recognized as |
/v2/insights place + filter.location + venue tag |
Candidate restaurants, cafes or bars around the fair center |
filter.results.entities + signal.interests.entities |
Scores the same candidates once per person, so people can be compared |
feature.explainability |
"Person 1 matches via Barbie": the reason comes from Qloo's data, not from an LLM |
filter.type=urn:heatmap |
One taste heatmap per person; the cell-wise minimum picks between commute-fair centers and shades the map |
/v2/analysis/compare |
"What you have in common" for the group |
filter.price_level.max |
Budget filter mapped from the existing price range field |
What the agent does. MeetSpotAgent (a ReAct agent over DeepSeek) gets a new tool, group_taste_rank. Given a group with tastes it geocodes everyone, computes the fair center, calls the tool, and explains its pick person by person using only what the tools returned. On the results page, Ask the agent runs it on the same group and shows each tool call it made.
What changed for the hackathon (all after 2026-09-30): the Qloo client (app/tool/qloo_client.py), per-person maximin ranking and taste heatmap in the recommender, the agent tool and English agent mode, per-person taste inputs with a New York demo group, the taste card / fit bars / left-out venues on the results page, and the Ask the agent card. Requests without tastes behave exactly as before.
Try it: open the finder, click Try a demo group in New York, then Find Fair Midpoint. Taste searches are not counted against the free daily limit during judging.
A geometric midpoint can still be unfair: one person may have a direct train while another needs two transfers. On the international (Google Maps) path, each participant can set a max commute in minutes. MeetSpot then takes several candidate centers, asks Google Routes (computeRouteMatrix) for each person's real travel time to each one, and keeps the candidate that breaks no one's limit. If every candidate breaks some limit, it picks the one with the smallest total overage, and treats an unreachable leg as the worst case. The results page shows which candidates were accepted or rejected and why.
Requests without commute limits, and the Amap (China) path, behave exactly as before. Code: app/tool/google_directions_client.py and _verify_commute_fairness in app/tool/meetspot_recommender.py.
MeetSpot is an AI Agent - it makes autonomous decisions based on request complexity, not just executes searches.
User Request
│
┌──────────────┴──────────────┐
│ Complexity Router │
│ (Autonomous Decision) │
└──────────────┬──────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ │ ▼
┌─────────────────┐ │ ┌─────────────────┐
│ Rule Mode │ │ │ Agent Mode │
│ Rules + LLM │ │ │ ReAct agent │
│ Deterministic │ │ │ LLM-Enhanced │
└────────┬────────┘ │ └────────┬────────┘
│ │ │
└─────────────────────┼───────────────────┘
│
┌──────────────┴──────────────┐
│ 5-Step Processing │
│ Pipeline │
└──────────────┬──────────────┘
│
┌──────────┬──────────┬────┴────┬──────────┬──────────┐
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
Geocode Center POI Ranking HTML Result
Calc Search Gen
The Agent autonomously decides which processing mode to use:
| Factor | Score |
|---|---|
| Locations | 3 locations +15, 4 or more +30 |
Venue types in keywords |
2 types +12, 3 or more +25 |
| Special requirements | 2 matched +15, 3 or more +25, or +20 for a long free-text description (over 50 characters) |
| Filters | +5 each for min_rating, max_distance under 10 km, price_range |
- Score < 40: Rule Mode (deterministic rules, faster)
- Score >= 40: Agent Mode (LLM reasoning, semantic understanding). If the agent times out or errors, the request falls back to Rule Mode.
The scoring lives in assess_request_complexity in api/index.py.
Final Score = Rule Score × 0.4 + LLM Score × 0.6
The LLM analyzes semantic fit between venues and requirements, then blends with rule-based scoring. Results include Explainable AI visualization showing the agent's reasoning process.
| Step | Function | Details |
|---|---|---|
| Geocode | Address → Coordinates | 60+ smart mappings (universities, landmarks) |
| Center Calc | Fair point calculation | Spherical geometry for accuracy |
| POI Search | Venue discovery | Concurrent async search, auto-fallback |
| Ranking | Multi-factor scoring | Base(30) + Popularity(20) + Distance(25) + Scenario(15) + Requirements(10) |
| HTML Gen | Interactive map | Amap JS API (China) or Google Maps JS API (international) |
# Clone and install
git clone https://github.com/calderbuild/MeetSpot.git && cd MeetSpot
pip install -r requirements.txt
# Configure (get key from https://lbs.amap.com/)
cp config/config.toml.example config/config.toml
# Edit config.toml and add your AMAP_API_KEY
# Run
python web_server.pyMeetSpot has a macOS desktop build (Electron, in desktop/) built for RevenueCat Shipaton 2026. The free tier is one search a day; a MeetSpot Pro entitlement through RevenueCat removes that limit.
Status: the purchase flow works end to end against the RevenueCat Test Store only. The app is not on any store and does not take real payments yet; you run it from source.
How the purchase flow works:
- The app keeps an anonymous RevenueCat app user id in localStorage and sends it as
X-RC-App-User-Idwith every search. - When the free search is used up, the server answers
need_payment. The app then loads@revenuecat/purchases-jsand callspresentPaywall()for the current offering. - After the purchase, the app reruns the search. The server never trusts the client: it calls RevenueCat REST
GET /v1/subscribers/{id}with the secret key and skips the quota only when themeetspot_proentitlement is active (app/payment/revenuecat.py). If that lookup fails, the request falls back to the normal quota and is never granted. - On launch the app calls
getCustomerInfo(). An activemeetspot_proshows a MeetSpot Pro badge; otherwise a Go Pro button opens the same paywall. The badge is display only, and the server still decides on every search.
sequenceDiagram
participant App as macOS app (Electron)
participant API as MeetSpot server (FastAPI)
participant RC as RevenueCat
App->>API: POST /api/find_meetspot + X-RC-App-User-Id
API->>RC: GET /v1/subscribers/{id} (secret key)
RC-->>API: entitlements
alt meetspot_pro active
API-->>App: results, no quota
else not active or lookup failed
API-->>App: need_payment once the free search is used
App->>RC: presentPaywall(), Test Store purchase
App->>API: rerun the search
end
| File | What it does |
|---|---|
desktop/main.js, desktop/preload.js |
Electron shell; the preload sets window.MEETSPOT_PLATFORM = "macos" |
public/meetspot_finder.html |
App user id, Web SDK paywall, automatic retry, Pro badge |
app/payment/revenuecat.py |
Server-side entitlement check; any failure falls back to the free limit |
api/index.py |
Quota gate in find_meetspot, /api/config/revenuecat |
tests/test_revenuecat.py |
12 tests against a mocked RevenueCat |
Run it locally:
# 1. Server (needs AMAP_API_KEY / GOOGLE_MAPS_API_KEY + LLM key as in Quick Start)
export REVENUECAT_PUBLIC_KEY=test_xxx # Test Store public API key
export REVENUECAT_SECRET_KEY=sk_xxx # v1 secret API key (server only)
uvicorn api.index:app
# 2. Desktop app (second terminal)
cd desktop && npm install && npm start
# Or build MeetSpot.app with its icon (ad-hoc signed, written to desktop/dist/)
cd desktop && npm run packageRevenueCat setup: create a project (it comes with a Test Store), an entitlement meetspot_pro, a Test Store product attached to it (a one-time purchase, since Test Store subscriptions expire after about 25 minutes), add it to the default offering, and configure a paywall on that offering.
To make a test purchase, run one search (free), then run a second one. The paywall appears. Pick the product and choose the successful-purchase option in the Test Store dialog. The search reruns and goes through, and the customer shows meetspot_pro active in the RevenueCat dashboard.
Known limitation: the app user id is anonymous and device-local. Anyone who learns that id could reuse its entitlement. The production fix is to tie the id to a MeetSpot account (app/auth). Without the header or the RevenueCat keys, MeetSpot behaves exactly as before, and the web build keeps its existing payment flow.
The live web app gives each visitor (tracked by IP) one free search a day. After that, searches use credits bought through the existing web payment flow (302.ai checkout, api/routers/payment.py). Self-hosted instances use the same default (FREE_DAILY_LIMIT=1); set FREE_DAILY_LIMIT=0 to turn the limit off.
POST /api/find_meetspot
{
"locations": ["Peking University", "Tsinghua University", "Renmin University"],
"keywords": "cafe restaurant",
"user_requirements": "parking, quiet environment"
}Optional tastes (one string per location, English path only) turns on Qloo group taste ranking:
{
"locations": ["Times Square, New York", "Union Square, New York", "Grand Central Terminal, New York"],
"keywords": "restaurant",
"tastes": ["Taylor Swift, Barbie", "Metallica, John Wick", "Bad Bunny, Trader Joe's"],
"language": "en"
}Response:
{
"success": true,
"html_url": "/workspace/js_src/place_recommendation_20260929153459_1f2dace8.html",
"locations_count": 2,
"processing_time": 5.2,
"mode": "rule_llm",
"message": "Recommendation generated successfully"
}| Endpoint | Method | Description |
|---|---|---|
/api/find_meetspot_agent |
POST | Force Agent Mode (LLM reasoning); with tastes it calls Qloo through group_taste_rank and returns taste_ranking + tool_trace |
/api/ai_chat |
POST | AI customer service chat |
/health |
GET | System health check |
/docs |
GET | Interactive API documentation |
![]() Agent Chain-of-Thought |
![]() Interactive Map View |
![]() Homepage |
![]() Meeting Point Finder |
| Layer | Technologies |
|---|---|
| Backend | FastAPI, Pydantic, aiohttp, SQLAlchemy 2.0, asyncio |
| Frontend | HTML5, CSS3, Vanilla JavaScript, Boxicons |
| Maps | Amap (Gaode) for China + Google Maps Platform for international (auto-routed by language) |
| AI | DeepSeek (default deepseek-flash) via OpenAI-compatible API |
| Deploy | Docker (the live demo runs on Render) |
- Copy the environment template and fill in your keys:
cp .env.example .env - Build and run:
docker build -t meetspot . && docker run --env-file .env -p 8000:8000 meetspot - Open http://localhost:8000 and check http://localhost:8000/health
MeetSpot/
├── api/
│ └── index.py # FastAPI application entry
├── app/
│ ├── tool/
│ │ └── meetspot_recommender.py # Core recommendation engine
│ ├── config.py # Configuration management
│ └── design_tokens.py # WCAG-compliant color system
├── templates/ # Jinja2 templates
├── public/ # Static assets
└── workspace/js_src/ # Generated result pages
# Development server with hot reload
uvicorn api.index:app --reload
# Run tests
pytest tests/ -v
# Code quality (same checks as CI)
ruff check . && flake8 . --select=E9,F63,F7,F82Contributions are welcome:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
|
Email: johnrobertdestiny@gmail.com GitHub: Issues Blog: calderbuild.github.io |
Personal WeChat |
WeChat Group |
Special thanks to AIGC Link for the promotions on XiaoHongShu.
- Tie the RevenueCat app user id to a MeetSpot account so a Pro entitlement cannot be reused from another device
- Enterprise meeting planning mode (team offsites, external meetings with special requirements)
- A benchmark suite for recommendation quality
MIT License - see LICENSE for details.











