The Passion Engine: How NomadAtlas Will Tap You on the Shoulder When the Conditions Are Right
A look at the Passion engine — a Celery-driven scheduler that watches weather, marine, and trail data on your behalf and pings you over WebSocket the moment your hobby becomes possible.
Most digital nomad tools answer the easy question — where should I go? The Passion engine answers the harder one: now that you're here, what should you actually do with your one wild and precious Saturday? It's a small feature with a big premise — that hobbies are the gravity that keeps the nomad lifestyle from spinning loose, and that an app should help you protect them.
The problem nobody names
Nomads burn out. Not from work — from weightlessness. A life of cafés, Wi-Fi, and laptop screens with nothing pulling you out of them. The ones who last all have the same trick: they show up in every new city with a wetsuit, a saddle, or boots already packed. Hobbies are the gravity. They're the thing that gets you out the door, into the place, and into your own body.
The catch is that hobbies have conditions. You can't dive in 2-meter swell. You don't want to ride 80 km in a thunderstorm. The trail above 3,000 m might be closed in May. In a city you've lived in for years you know all this in your bones. In a city you've lived in for nine days, you don't know any of it — and so you skip the activity, because checking is friction.
The flywheel
The Passion page is the front door. A user adds a hobby — Cycling, Diving, Trekking — and the system stores not just the name but a small declarative spec: I am viable when these conditions hold. From that moment on, the user does nothing. The backend takes over.
What makes this a flywheel and not just a pipeline is the feedback edge. Every push gets a one-tap reply: I went / I skipped / not for me. Those signals tune the predicates to the individual. Some divers are happy in 2-meter visibility. Some won't touch under 8. The system learns your version, not the average version, and the recommendations get sharper the longer you use it.
Predicates as a contract
Each hobby is a small schema — a list of predicates that must all hold for the activity to be viable. New hobby = new schema, zero new infrastructure. The scheduler is hobby-agnostic; it just evaluates expressions against live data feeds.
- Composable — predicates combine with AND. New predicate types (
tide.window,aqi.lt,daylight.gte) are pure functions over a typedConditionsobject. - Personalizable — every predicate has a default threshold and an overridable user threshold. Defaults ship safe; user adjustments tighten or loosen.
- Forecast-aware — predicates evaluate against forecasts up to 7 days out, not just the current hour. The system suggests when, not just whether.
- Location-aware — every match is tied to a named place near the user ("Sail Rock", "Son Tra", "Cap de Formentor") because where is half the recommendation.
The runtime — Celery + WebSocket
The frontend is what you've already met: a React 19 SPA, served from a Cloudflare Worker. The backend that powers Passion lives behind the same /api/* proxy and adds two pieces — a Celery beat scheduler that wakes every 30 minutes, and a WebSocket gateway that holds an open connection while the app is in the foreground.
- Beat schedule — every 30 min Celery dispatches a
evaluate_user_hobbies(user_id)task per active user. Cheap, parallelizable, idempotent. - Evaluator — for each hobby it pulls the relevant feed slice (forecast for the next 7 days at the user's coords) and runs the predicates. Match found → produce an
Invitationrow. - Deduplication — invitations are keyed
(hobby, place, day_window)so the same Saturday dive doesn't get pushed five times across five beat ticks. - Delivery — if the user has an open WebSocket, push immediately with rich payload. Otherwise queue and replay on next connect. Optional fallback to web push later.
- Ack loop — the user's went / skipped / not for me tap closes the loop and writes a training row against that predicate set.
The magic moment
Great weather ahead — how about hiking Son Tra on Saturday? Wind 9 km/h, UV 6, daylight 12h, no thunder risk through Sunday.
That's the line you screenshot and send to a friend. Not go hiking — that hike, this weekend, because of these conditions. It's concrete, local, named, and timed. It's what a friend who knows the area would say. And it scales to every city you ever live in, because the friend is the engine.
The page is the input form
The Passion page in the app today already does the unglamorous part — it makes adding a hobby feel like a celebration rather than a settings tweak. Bold gradients, illustrated cards, last-activity stats, level progression, a next goal line. None of that is decoration. Every field is a feature vector for the recommendation engine.
- Hobby type → which schema to load
- Last activity stats → the user's current performance band
- Level + experience → which sites to suggest (a Veteran trekker gets longer routes)
- Next goal → a second axis the engine optimizes for ("on the way to your Annapurna goal? Here's a 4-day acclimatization trek that matches the elevation profile")
Why this matters
Lots of apps will sell you a weather alert. The Passion engine is a different product. It's a portable local friend who happens to know the forecast, the tides, the trail status, and your personal thresholds — and who only speaks up when there's a reason. Silence is the feature. The tap on the shoulder is the product. The data flywheel is the moat.
The Passion page on the dashboard is the trojan horse. It looks like a pretty hobby tracker. It's actually the input contract for an engine that turns the loneliest part of nomad life — I'm in a new city and I have no idea what to do this weekend — into a notification that lands at exactly the right moment.