Skip to content

MahammadNuriyev62/znum-trip-planner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

131 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Z-Number Trip Optimizer

An MCP server that plans optimized sightseeing trips using Z-number fuzzy arithmetic. It models uncertainty in travel times, costs, and POI ratings, using real traffic data from the Google Routes API.

Based on: "Z-information-based designing a sustainable tourism travel trip" (Infus 2024).

Technical report (PDF) with full math, architecture diagrams, and engineering details.

Demo

video2.mov

How it works

The system separates concerns between Claude (conversational data collector) and the MCP server (route engine and optimizer).

Claude gathers POI metadata through conversation: coordinates and place IDs from Google Maps tools, opening hours and ticket prices from web search, ratings and review counts, visit duration estimates, and local taxi rates. Claude goes back and forth with the user, clarifying preferences and resolving ambiguities.

The MCP server independently handles all route and traffic data once Claude calls plan_trip:

  1. OSRM distance matrices (free) to determine which POI pairs are close enough to query
  2. Google Routes API (cached) for traffic-aware travel times, polylines, transit details, and fares across 6 departure hours
  3. Z-number construction from raw traffic observations (uncertainty from duration spread, reliability from alternative route agreement)
  4. Multi-objective optimization (ALNS, Local Search, or Differential Evolution) balancing sightseeing score, carbon footprint, travel time, and budget
  5. Interactive HTML map with polylines, timeline slider, and route comparison tabs

Claude never sees or manipulates route or traffic data directly.

What makes it different

Feature Description
Z-number loss function Hellinger distance + fuzzy dominance constraints, not crisp approximations. Travel times carry both a fuzzy value (A) and a reliability score (B)
Real traffic data Google Routes API queried at 6 departure hours; duration spread across hours and alternative routes naturally captures traffic uncertainty
Mode-agnostic optimizer Works with TravelOption lists, not hardcoded transport categories. Adding a new mode requires no optimizer changes
Marginal carbon model Only private vehicles (DRIVE) have non-zero carbon. Public transit runs on fixed schedules regardless of individual ridership
Two-tier caching SQLite with exact coordinate match + Google place_id fallback. Repeated trips to the same places hit cache instantly

Connecting to Claude.ai

Add to your Claude.ai MCP settings:

{
  "mcpServers": {
    "trip-optimizer": {
      "type": "url",
      "url": "https://mcp-server-production-b0ae.up.railway.app/mcp"
    }
  }
}

Then ask Claude to plan a trip. It will gather your preferences, research places, and call the optimizer.

Tools

plan_trip

Full optimization pipeline. Key parameters:

Parameter Default Description
hotel_lat, hotel_lon, hotel_name required Hotel location
pois required List of POI dicts (name, lat, lon, score, entrance_price, spend_hours, open_hour, close_hour, review_count, place_id)
optimizer "alns" "ls" (~5s), "alns" (~30s), "de" (~2min)
budget 100.0 Maximum budget in local currency
start_hour 8.0 Departure time (decimal, e.g. 9.5 = 9:30 AM)
max_hours 12.0 Maximum trip duration
max_pois 7 Maximum POIs to visit
timezone_offset 4.0 UTC offset for the city
allowed_modes all ["DRIVE", "TRANSIT", "WALK", "BICYCLE"]
drive_base_fare 3.0 Taxi flag-drop fare
drive_cost_per_km 1.0 Taxi per-km rate
drive_cost_per_min 0.3 Taxi per-minute rate
city "" City name for transit fare estimation fallback

Z-number construction

Every quantity in the optimizer is a Z-number Z = (A, B) where A is a trapezoidal fuzzy number for the value and B is a trapezoidal fuzzy number for reliability.

Travel times

The same route is queried from the Google Routes API at 6 departure hours (8:00, 10:30, 12:30, 14:30, 17:30, 22:00). At query time, the nearest departure-hour observation is selected:

  • A component spans the range of durations returned by Google (primary + alternative routes)
  • B component is determined by observation count — how many of the 6 departure hours a route was actually observed at. A route seen at all 6 hours gets B_HIGH = [0.85, 0.95, 0.95, 1.0]; a route seen at only 1 hour gets B_FLOOR = [0.65, 0.7, 0.7, 0.8]. Intermediate counts are linearly interpolated.

Routes that only appear at certain hours (e.g., a transit line running only during rush hour) naturally get lower B, reflecting reduced confidence that the route is available at query time. No explicit rush-hour penalty is needed because observation frequency itself encodes availability uncertainty.

POI scores

A comes from a linguistic mapping of the Google rating (1-5). B comes from review count: 1000+ reviews = very reliable, <20 = very uncertain.

Costs

Transport costs (taxi fares, transit fares) and entrance prices are crisp values with no artificial fuzzification. Taxi fares use the metered formula (base + per-km + per-min); transit fares come from Google or a per-km fallback; entrance prices are provided by the LLM.

Cost models

  • DRIVE: Taxi fare formula F = base_fare + cost_per_km × distance + cost_per_min × time (Yuan et al. 2017). The per-minute component captures congestion impact.
  • TRANSIT: Google-provided fare when available (parsed from localized currency text). When Google has no fare data, a multi-strategy estimator tries: Transitland GTFS data, per-city/vehicle-type hardcoded fares, flat city fares, then per-km fallback. Hardcoded fares support flat (Baku 0.60 AZN, Dubai 3.0 AED), per-km (Delhi metro ~10 + 1.8/km INR), and per-stop (Kyoto bus ~170 + 30/stop JPY) pricing. All fares assume single-ride pricing.
  • WALK / BICYCLE: Free, zero carbon.

Loss function

L = w_s · Hellinger(score, ideal)        # maximize sightseeing quality
  + w_c · carbon / ref_carbon            # minimize carbon footprint (crisp)
  + w_t · Hellinger(travel_time, 0)      # minimize travel time (prevents zigzags)
  + w_r · avg_B_penalty                  # prefer reliable transport (duration B)
  + λ₁  · time_violation                 # respect time budget
  + λ₂  · hours_violation                # respect opening hours
  + λ₃  · budget_violation               # respect cost budget (crisp)

Default weights: w_s = 0.70, w_c = 0.20, w_t = 0.10, w_r = 0.10, λ₁ = λ₂ = λ₃ = 1.0.

Z-number terms (score, travel time, time/hours constraints) use Hellinger distance and fuzzy dominance. Carbon and budget are crisp because their inputs (distance, emission factors, prices) have no genuine uncertainty. Score A values are pre-normalized to [0, 1] so the reliability component (B) is not dominated.

Optimization algorithms

  • ALNS (default): Adaptive Large Neighborhood Search with simulated annealing. Destroy operators (random, worst, segment removal) and repair operators (greedy, regret, random insertion) with adaptive weights. Best balance of quality and speed.
  • Local Search: Random restarts with first-improvement hill climbing. Neighborhood: swap, 2-opt, option-swap, insert, remove. Fast but less thorough.
  • Differential Evolution: Population-based with priority encoding. Most thorough but slowest.

All optimizers use the Li et al. (2023) analytical engine (Znum.fast()) for ~5x speedup on triangular Z-number arithmetic.

Project structure

mcpserver/
  server.py          # FastMCP server with plan_trip tool
  google_routes.py   # Google Routes API client (traffic-aware, transit details)
  osrm_client.py     # OSRM client for distance matrices
  cache.py           # SQLite cache with place_id fallback
  pair_selector.py   # Adaptive pair selection from OSRM distances
  z_convert.py       # Traffic data → Z-number conversion (nearest-hour lookup)
  html_bundle.py     # Interactive map HTML generator
  templates/
    trip_map.html    # Standalone map template (served via URL)
    trip_app.html    # MCP App template (embedded in Claude.ai)
core/
  models.py          # POI, Route, TripConfig, TravelDataProvider protocol
  z_loss.py          # Z-number loss (Hellinger distance + fuzzy dominance)
  optimizer.py       # ALNS, Local Search, Differential Evolution
adapters/
  api_provider.py    # Google/OSRM → TravelDataProvider adapter
  excel_loader.py    # Excel data adapter (offline/paper replication)
ui/
  app.py             # Streamlit dashboard (offline mode)
docs/
  technical_report.pdf  # Full technical report
tests/               # Unit + integration tests

Self-hosting

Requirements

  • Python 3.12+
  • Google Maps API key (Compute Routes API enabled)
  • znum package

Docker

docker build -t trip-optimizer .
docker run -p 8000:8000 \
  -e GOOGLE_MAPS_API_KEY=your_key \
  -e CACHE_DB_PATH=/data/cache.db \
  -v trip-data:/data \
  trip-optimizer

Railway

  1. Fork this repo
  2. Create a new Railway project from the fork
  3. Set environment variables: GOOGLE_MAPS_API_KEY, CACHE_DB_PATH (e.g. /data/cache.db)
  4. Add a persistent volume mounted at /data
  5. Deploy (auto-builds from Dockerfile on push)

Local development

pip install -r requirements.txt
export GOOGLE_MAPS_API_KEY=your_key
python -m mcpserver.server

Offline mode (Streamlit)

For paper replication or offline use with Excel data:

streamlit run ui/app.py --server.port 8504 --server.headless true

Running tests

# Unit tests (fast)
pytest tests/test_core.py tests/test_adapters.py tests/test_drive_fare.py tests/test_mcp_z_convert.py tests/test_mcp_cache.py -v

# Full suite including integration (slow, 30+ min)
pytest tests/ -v

Known limitations

  • Toll prices not included in cost estimation (would require additional API field mask, increasing per-request cost)
  • Transit fare coverage: Google fare data is used when available; fallback estimator has hardcoded fares for Baku, Dubai, Delhi, and Kyoto, and queries Transitland for other cities. Cities not in Transitland or the hardcoded table fall back to a per-km estimate
  • Transit fare model assumes single rides: day passes, tourist cards, multi-ride discounts, and free-transfer policies are not modeled. Transfer fares are summed per segment
  • No currency conversion: all costs (entrance fees, taxi fares, transit fares, budget) must be in the same local currency
  • Single-day trips only; multi-day itineraries not supported
  • Temporal cache granularity: traffic data cached for 30 days without weekday/weekend distinction
  • Opening hours rely on Claude's data collection; may use pretrained knowledge rather than live sources
  • OSRM public instance only has the car profile compiled; foot/bike endpoints return car-speed durations, so the code recalculates durations using realistic walking (5 km/h) and cycling (15 km/h) speeds. Distances use the car road network, which may slightly overestimate walkable paths
  • No transport mode preference: modes can only be allowed or disallowed via allowed_modes; there is no way to weight or prefer one mode over another (e.g., "prefer transit over driving")

Future work

  • Multi-ride transit passes: model day passes, tourist cards, and weekend cards to optimize transit spending across multiple legs
  • Zone-based fare calculation: support distance/zone-based pricing (e.g., Dubai metro zones, London zones) instead of flat per-ride fares
  • Transfer discount policies: model free or discounted transfers within time windows (e.g., BakuKart 30-min free transfer)
  • Multi-day trip itineraries: extend the optimizer to plan across multiple days with hotel returns
  • Currency-aware cost normalization: support mixed-currency inputs and automatic conversion

License

This work is licensed under a Creative Commons Attribution 4.0 International License.

CC BY 4.0

You are free to share and adapt this work for any purpose, including commercially, as long as you give appropriate credit. If you use this software or build upon it in academic work, you must cite the original work (see below).

Citation

If you use this software in your research, please cite:

@software{nuriyev2026znum_trip_optimizer,
  author    = {Nuriyev, Mahammad},
  title     = {Z-Number Trip Optimizer: An MCP Server for Fuzzy Multi-Objective Sightseeing Trip Planning},
  year      = {2026},
  url       = {https://github.com/MahammadNuriyev62/znum-trip-planner-2}
}

And the underlying research paper:

@inproceedings{aliev2024zinformation,
  author    = {Aliev, R.A. and others},
  title     = {Z-information-based designing a sustainable tourism travel trip},
  booktitle = {Infus 2024},
  year      = {2024}
}

References

  1. Aliev, R.A. et al. (2024). "Z-information-based designing a sustainable tourism travel trip." Infus 2024.
  2. Yuan, H. et al. (2017). "Modeling and Analyzing Taxi Congestion Premium." Transportation Research Record, 2634(1), 9-16.
  3. Li, Y. et al. (2023). Analytical operations on Z-numbers with triangular fuzzy components.
  4. Google Compute Routes API v2 documentation (2026).
  5. OSRM Table API documentation.