{"slug": "how-we-built-a-travel-guide-from-open-data-with-claude-code", "title": "How we built a travel guide from open data with Claude Code", "summary": "The Wanloria team built a free bilingual (Spanish/English) travel web app covering verified places in 71 cities, 1-3 day itineraries, free-attraction lists and signposted hiking trails, largely by pair-programming with Claude Code and Cursor. The team's focus was on verification rules that prevent the LLM from inventing facts: descriptions are generated only from verified fields, hiking times follow DIN 33466, difficulty uses the MIDE 1-5 scale, and every public page is server-rendered HTML with JSON-LD, hreflang and an llms.txt file. They flagged an open issue where a museum free only on Sundays was mislabeled as free, and are moving from a boolean to a richer price model (free, free_at_times, paid).", "body_md": "**Disclosure:** this post is by the Wanloria team (free, no ads, no affiliate links). It was drafted with AI assistance and fact-checked against the live site, per dev.to's guidelines on AI-assisted posts.\n\n**TL;DR:** [Wanloria](https://wanloria.cloud/?utm_source=devto&utm_medium=article&utm_campaign=aeo-devto) is a free, bilingual (ES/EN) travel web app: verified places in 71 cities, 1-3 day itineraries with times, \"free things\" lists, signposted hiking trails with GPX and a weekly \"this weekend\" page. We built it largely by pair-programming with Claude Code and Cursor. The interesting part isn't the AI, though. It's the rules we gave it so it couldn't make things up.\n\nMost \"things to do in X\" pages are rewritten from other \"things to do in X\" pages. We wanted every fact on a page (opening hours, price, website, whether a trail is circular) to trace back to a source, or not be shown at all.\n\n`/lugar/wiki-Q1140249/templo-de-debod` and `/lugar/osm-r4544509/museo-cerralbo`.\nDescriptions are generated by an LLM **only from the verified fields** of that place, never copying sentences from the source. If a fact isn't verified, the template doesn't render it. Every description is reviewed before it's published.\n\nHiking time follows **DIN 33466**, with no stops:\n\n```\n// DIN 33466: 4 km/h on the flat, 300 m/h up, 500 m/h down.\nfunction dinHours(km, ascentM, descentM) {\n  const horizontal = km / 4;\n  const vertical = ascentM / 300 + descentM / 500;\n  return Math.max(horizontal, vertical) + Math.min(horizontal, vertical) / 2;\n}\ndinHours(12.6, 493, 395); // 4.37 h â†’ shown as \"4 h 20 min\"\n```\n\nThat's exactly what the live page for the [PR-C 164 near Barcelona](https://wanloria.cloud/ruta/trail-1948977-41c0d0/collserola-des-de-sant-just?utm_source=devto&utm_medium=article&utm_campaign=aeo-devto) shows (12.6 km, +493 m, 4 h 20 min, MIDE 3/5). Difficulty uses the **MIDE** 1-5 effort scale. Trails are only published if they're signposted (GR/PR/SL or equivalent), within 18 km of the city, doable in a day, and if the OSM geometry matches the official distance. Loose stages of long-distance routes are dropped.\n\nItineraries split a city's top places by area, order each day from most important to nearest, and estimate walking as **straight-line distance + 30 % for streets at 4.5 km/h**, with a lunch break. Visit duration is estimated by place type (a big museum isn't a viewpoint) and adjusted when the place publishes its own figure.\n\nThe app itself is a SPA, but every public page (city, place, trail, 1/2/3-day plan, collections like `/gratis` and `/museos`, and `/este-finde`) is **server-rendered HTML** with JSON-LD (` TouristAttraction`, `ItemList`, `FAQPage`, `BreadcrumbList`), hreflang es/en and a sitemap of ~7,000 URLs. There's also an [`llms.txt`](https://wanloria.cloud/llms.txt) and a `llms-full.txt` with the core facts per city, each linking to its page.\n\nVerification is only as good as the field it verifies. One real example: a museum whose general ticket costs â‚¬3 but is free on Sundays got tagged \"free\", because the source said \"entrada gratuitaâ€¦\" for certain time slots. The fix we're moving to is a richer price model (`free`, `free_at_times`, `paid`) rather than a boolean. Elevation from a 90 m model can be Â±10 % off a GPS track. Opening hours drift, so every place page keeps the source link and users can report errors to [hola@wanloria.cloud](mailto:hola@wanloria.cloud).\n\nFeedback very welcome, especially on the price model and the trail filters.", "url": "https://wpnews.pro/news/how-we-built-a-travel-guide-from-open-data-with-claude-code", "canonical_source": "https://dev.to/wanloria/how-we-built-a-travel-guide-from-open-data-with-claude-code-2eaa", "published_at": "2026-10-03 19:55:15+00:00", "updated_at": "2026-10-03 20:07:51.869334+00:00", "lang": "en", "topics": ["ai-tools", "generative-ai", "structured-data", "agent-protocols", "ai-search"], "entities": ["Wanloria", "Claude Code", "Cursor", "OpenStreetMap", "DIN 33466", "MIDE"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/how-we-built-a-travel-guide-from-open-data-with-claude-code", "markdown": "https://wpnews.pro/news/how-we-built-a-travel-guide-from-open-data-with-claude-code.md", "text": "https://wpnews.pro/news/how-we-built-a-travel-guide-from-open-data-with-claude-code.txt", "jsonld": "https://wpnews.pro/news/how-we-built-a-travel-guide-from-open-data-with-claude-code.jsonld"}}