<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Travelexe edge91</title>
    <description>The latest articles on DEV Community by Travelexe edge91 (@travelexe_edge91_b08eec3f).</description>
    <link>https://dev.to/travelexe_edge91_b08eec3f</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4218549%2F08af6847-9994-4906-aad3-e651c23b435c.jpeg</url>
      <title>DEV Community: Travelexe edge91</title>
      <link>https://dev.to/travelexe_edge91_b08eec3f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/travelexe_edge91_b08eec3f"/>
    <language>en</language>
    <item>
      <title>Hotel mapping: giving one hotel one ID across supplier feeds</title>
      <dc:creator>Travelexe edge91</dc:creator>
      <pubDate>Sat, 31 Oct 2026 11:28:47 +0000</pubDate>
      <link>https://dev.to/travelexe_edge91_b08eec3f/hotel-mapping-giving-one-hotel-one-id-across-supplier-feeds-4ck1</link>
      <guid>https://dev.to/travelexe_edge91_b08eec3f/hotel-mapping-giving-one-hotel-one-id-across-supplier-feeds-4ck1</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: written by the TraveleXe team. We build Syncro, the hotel and room mapping system mentioned below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you sell hotels from more than one supplier, you've seen this: the same hotel appears four times in your search results, with four slightly different names, two addresses and coordinates 80 metres apart. Your "cheapest price" sort is now comparing a hotel against itself.&lt;/p&gt;

&lt;p&gt;That's the hotel mapping problem. Here is how it's usually solved, and what we learned building it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem in one table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Supplier&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;th&gt;Lat, Lng&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Grand Palm Resort &amp;amp; Spa&lt;/td&gt;
&lt;td&gt;12 Beach Rd&lt;/td&gt;
&lt;td&gt;25.1180, 55.1350&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;The Grand Palm Resort&lt;/td&gt;
&lt;td&gt;Beach Road 12&lt;/td&gt;
&lt;td&gt;25.1186, 55.1342&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;GRAND PALM RESORT AND SPA DUBAI&lt;/td&gt;
&lt;td&gt;Jumeirah Beach Rd, 12&lt;/td&gt;
&lt;td&gt;25.1179, 55.1351&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A human sees one hotel. A database sees three.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: one canonical ID
&lt;/h3&gt;

&lt;p&gt;The core design decision is a canonical record per physical property, with every supplier's hotel code linked to it. In Syncro this is a permanent global Mapping ID per hotel, and every supplier hotel ID points to it. Everything downstream (search, price comparison, reporting) uses the canonical ID.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: don't compare everything with everything
&lt;/h3&gt;

&lt;p&gt;Comparing every new hotel against every known hotel is O(n²) and slow. The standard technique is &lt;strong&gt;blocking&lt;/strong&gt;: only compare candidates that are near each other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Illustrative only: geo blocking with a coarse grid
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;block_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;   &lt;span class="c1"&gt;# ~1 km cells
&lt;/span&gt;    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;block_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: score, don't guess
&lt;/h3&gt;

&lt;p&gt;For each candidate pair, combine several weak signals into one score: normalised name similarity, address similarity, distance, star rating, chain or brand, postcode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Illustrative only: a weighted match score
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hotel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spa&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# e.g. token-set ratio
&lt;/span&gt;    &lt;span class="n"&gt;s_addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address_norm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address_norm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s_geo&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;haversine_m&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# 0 beyond 250 m
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s_addr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s_geo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Syncro's page describes the same family of signals: fuzzy logic, geospatial intelligence and attribute comparison, with AI used to detect and merge duplicates and to keep learning as feeds change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: confidence bands and a human queue
&lt;/h3&gt;

&lt;p&gt;A single threshold is a mistake. Use three bands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High confidence:&lt;/strong&gt; link automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Middle band:&lt;/strong&gt; send to a review queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low:&lt;/strong&gt; treat as a new property.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The middle band is where the quality of a mapping system shows. Syncro attaches an AI confidence score to each mapping and supports manual override and approval workflows where needed, which is the same pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: rooms are the harder half
&lt;/h3&gt;

&lt;p&gt;Matching hotels fixes duplicates in the list. It doesn't fix price comparison. "Deluxe King Sea View", "DLX KG SV" and "Superior Double, Sea Facing" may or may not be the same room. Room mapping normalises names, amenities, occupancy and bed types into master room types. Syncro does this as a separate Room Mapping API alongside the Hotel Mapping API.&lt;/p&gt;

&lt;p&gt;Without room mapping, the cheapest result is often just the most loosely described room.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: mapping is a process, not a project
&lt;/h3&gt;

&lt;p&gt;Suppliers add hotels daily and rename room types without notice. A mapping system needs to auto-map new hotels as they arrive, continuously re-validate existing mappings and detect room category changes. Syncro's page lists all three. A one-off cleanup decays within months.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build or buy?
&lt;/h3&gt;

&lt;p&gt;Build it if you have data engineers, few suppliers and a reason to own the matching logic. Buy it if mapping isn't your product. Options include GIATA, Vervotech, Gimmonix and Syncro, usually delivered by API. In any evaluation, map &lt;strong&gt;your own&lt;/strong&gt; two or three supplier feeds in the trial: accuracy on someone else's data tells you little.&lt;/p&gt;

&lt;p&gt;We wrote up a fuller comparison of hotel mapping options and the tests to run in a demo here: &lt;a href="https://www.travelexe.com/best-hotel-mapping-software/" rel="noopener noreferrer"&gt;https://www.travelexe.com/best-hotel-mapping-software/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>data</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>One hotel API vs ten supplier integrations: what we learned</title>
      <dc:creator>Travelexe edge91</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:34:47 +0000</pubDate>
      <link>https://dev.to/travelexe_edge91_b08eec3f/one-hotel-api-vs-ten-supplier-integrations-what-we-learned-3dc9</link>
      <guid>https://dev.to/travelexe_edge91_b08eec3f/one-hotel-api-vs-ten-supplier-integrations-what-we-learned-3dc9</guid>
      <description>&lt;p&gt;&lt;em&gt;From the TraveleXe engineering team. We build APINex, a unified travel API — this post is about the trade-offs, including when you shouldn't use one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you're building a travel booking product, you'll hit the supplier question early: integrate each hotel supplier directly, or put one aggregation layer in front of all of them?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct integrations&lt;/strong&gt; give you full control of each supplier's features and no middle layer. The cost: every supplier has its own schema, auth, error codes, rate-limit behaviour and certification process. Ten suppliers is ten of everything, and they all change on their own schedules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A unified API&lt;/strong&gt; gives you one schema, one auth model and normalised responses. The costs are real too: you depend on the aggregator's uptime and its mapping quality, and supplier-specific features may arrive later.&lt;/p&gt;

&lt;p&gt;What mattered most for us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normalisation is the product.&lt;/strong&gt; Converting ten XML dialects into one JSON response is the easy part. Knowing that two responses describe the same hotel and the same room (mapping) is the hard part.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching is a correctness problem, not just speed.&lt;/strong&gt; Cached availability that goes stale turns into failed bookings. Cache search, re-check price at pre-book, never cache the final confirmation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts per supplier.&lt;/strong&gt; One slow supplier shouldn't hold the whole search. We return what's ready and stream late results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bring-your-own-credentials&lt;/strong&gt; keeps your negotiated rates yours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When should you integrate directly? If you only need one or two suppliers, or a supplier's special feature is core to your product.&lt;/p&gt;

&lt;p&gt;Longer write-up with a decision table:&lt;br&gt;
&lt;a href="https://www.travelexe.com/one-hotel-api-vs-multiple-supplier-integrations/" rel="noopener noreferrer"&gt;https://www.travelexe.com/one-hotel-api-vs-multiple-supplier-integrations/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>travel</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
