A trip planner for people who know how they want to rest but not yet where. Write the trip in one sentence, get up to three genuinely different futures, and see what each one costs you.





Travel search starts from filters: a city, dates, a price range, a star rating. People do not think that way. They think "a week by a warm sea for two, under 180,000 rubles, no crowds, good food, lots of walking". Turning that into filters is the hard part, and booking sites leave it to the user.
Elsewhere starts from the sentence. It turns the request into explicit preferences, assembles whole trips from real data, and shows the trade-off behind every option instead of a ranked list of near duplicates. It plans; it never books or takes payment.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#fffdf8","primaryTextColor":"#1d2925","primaryBorderColor":"#174c3b","secondaryColor":"#ebe7dc","tertiaryColor":"#f3f0e8","lineColor":"#65706b","textColor":"#1d2925","noteBkgColor":"#ebe7dc","noteTextColor":"#3d4a45","actorBkg":"#fffdf8","actorBorder":"#174c3b","fontFamily":"Inter"}}}%%
flowchart LR
D[Dream text] --> P[Dream parser]
P --> DNA[Travel DNA]
DNA --> F[Futures A, B, C]
S[(Supply: catalogue, fares, climate, OSM)] --> F
F --> Sim[Simulator]
Sim --> F
F --> Fc[Forecast]
Fc --> Plan[Trip plan]
- Travel DNA. The parser turns the sentence into weighted preferences and hard constraints. Every dimension can be edited; each save creates a new version, and the user always has the last word.
- Futures. Up to three whole trips by archetype: the best match, one that is really cheaper, and one with a different type of geography. If an archetype cannot clear its threshold, the set is smaller and the response says why. Near duplicates never fill the gap.
- Experience Match. The score decomposes into per-preference contributions, so "88%" can be read line by line. Unknown values stay unknown instead of counting as neutral.
- Simulator. "Make it cheaper", "quieter", or a slider move. The original future is kept, a new version is created, and the delta in price and match is shown.
- Forecast. Risk rules fire only on a measurement or a labelled proxy: night noise from the distance to a primary road, a cold sea from marine data. Each risk carries severity, confidence and mitigations derived from the risk itself.
- The LLM's role is narrow. It reads and phrases text. Prices, scores and distances are computed in code. When the model is down or slow, a deterministic path answers and marks the result as degraded.
You need Git, Node.js 20+, and either Ruby 3.3+ or Docker.
git clone https://github.com/GoatWhistle/elsewhere.git
cd elsewhere
bin/setupbin/setup creates .env from .env.example, installs gems and prepares the database. Without a
suitable Ruby it builds the Docker image and runs PostgreSQL with PostGIS and Redis in Compose.
Start the API with local Ruby:
bin/rails serverOr, when setup chose Docker:
docker compose up appThen start the web app in another terminal:
npm install --prefix web
npm run dev --prefix webThe web app opens at http://localhost:5173, the API at http://localhost:3000. The default
SUPPLY_MODE=fixture works without API keys or a model. In development the web app can also run on
MSW mocks without the backend; set VITE_API_MODE=real to call the API.
| Variable | Purpose |
|---|---|
SUPPLY_MODE |
fixture for the offline corpus, live for real providers |
IGNAV_API_KEY, ORS_API_KEY |
Flight fares and routing in live mode |
LLM_BASE_URL, LLM_MODEL |
Any OpenAI-compatible endpoint, for example Ollama with qwen3:8b |
LLM_TIMEOUT_SECONDS |
Model timeout; a local 8B model needs about 30 s per dream |
bin/check runs Packwerk boundaries, RSpec, the TypeScript check and ESLint in one go.
npm test --prefix web runs the web tests.
| Data | Source | Used for |
|---|---|---|
| Properties | Catalogue harvested once and stored locally | Real attributes and a base price level |
| Coast, roads, POI, airports | OpenStreetMap in PostGIS | Distances and surroundings |
| Climate and weather | Open-Meteo | Climate normals, sea temperature, forecast |
| Flight fares | Ignav | Live fares in live mode, recorded responses in fixture |
| Room rate for exact dates | Calibrated model | Observed base level with modeled seasonality |
| Reviews | Not connected | No review-based conclusions are drawn |
The bundled corpus holds 408 properties across seven destinations: Anapa, Sochi, Kaliningrad, Kazan, Saint Petersburg, Caucasian Mineral Waters and Sheregesh.
packs/
shared/ value objects and the AI task runner
supply/ catalogue, fares, climate, geography and the rate model
planning/ Travel DNA, constraints, assembly, match and the simulator
foresight/ evidence, risk rules and mitigations
app/ HTTP controllers and background jobs
web/ React interface with a typed client and MSW mocks
spec/ backend unit, contract and end-to-end specs
docs/ product, architecture, research and the decision log
Logic lives in Packwerk packs, not in controllers. Dependencies point one way:
shared ← supply ← planning ← foresight, and only supply talks to the network. The HTTP API
follows docs/04_api/openapi.yaml, checked by a contract spec. The
documentation is in Russian.
- A planner, not a booking service: no offers, orders or payments.
- Room rates for specific dates include modeled seasonality and are labelled as modeled.
- Reviews are not connected, so review-based risks such as food quality stay out of the forecast.
livemode depends on provider keys and their availability.- The web interface is in Russian only.

Team GoatWhistle, 2026. Licensed under MIT.

















