Recipe: bulk POI scoring for a property catalog

Score every listing in your database for proximity to schools, transit, shops, etc. Run nightly, store the result, filter on it in your own search.

Scenario

You have 50 000 active listings. You want users to filter on “within 5 min walking of a school”. Calling Yatmo at search time is too slow and unnecessary; you want a precomputed score per listing, refreshed nightly.

Approach

  1. Iterate over your listings. For each, call /summary for its coordinates.
  2. Walk the response and pull out the travel time to the categories you care about.
  3. Store the score on the listing record. Reindex your search.
  4. Throttle. Sleep 50–100 ms between calls to be a polite client and avoid 429s.

Worker example

# Pseudo-shell: for a single listing.
# Real pipelines iterate; see the JS/PHP/C# examples for the loop.
curl -H 'LicenseKey: YOUR_KEY' \
  "https://be.yatmo.com/summary?latitude=$LAT&longitude=$LNG&language=EN" \
  | jq '.AvailableCategoriesAroundPosition'
// Node.js worker
import fs from 'node:fs/promises';

async function scoreListing(listing) {
    const url = `https://be.yatmo.com/summary?latitude=${listing.lat}&longitude=${listing.lng}&language=EN`;
    const res = await fetch(url, { headers: { LicenseKey: process.env.YATMO_KEY } });
    if (!res.ok) {
        console.error('Yatmo error', res.status, await res.text());
        return null;
    }
    const summary = await res.json();
    // Pick a metric: walking time to the nearest school.
    const schoolCat = summary.AvailableCategoriesAroundPosition
        .flatMap(c => c.SubCategories)
        .find(sc => sc.Label === 'School');  // localized name
    const walking = schoolCat?.Data?.[0]?.TravelDataElements
        ?.find(t => t.TravelMode === 2);  // 2 = Walking
    return walking?.TravelTime ?? null;     // seconds
}

const listings = JSON.parse(await fs.readFile('listings.json', 'utf8'));
for (const l of listings) {
    l.walkToSchoolSec = await scoreListing(l);
    await new Promise(r => setTimeout(r, 80));   // throttle 80ms
}
await fs.writeFile('scored.json', JSON.stringify(listings, null, 2));
// PHP worker (CLI)
$key = getenv('YATMO_KEY');
$listings = json_decode(file_get_contents('listings.json'), true);

foreach ($listings as &$l) {
    $url = "https://be.yatmo.com/summary?latitude={$l['lat']}&longitude={$l['lng']}&language=EN";
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ["LicenseKey: $key"]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $summary = json_decode(curl_exec($ch), true);
    curl_close($ch);
    // Walk the structure to find your metric; store it on the listing.
    $l['walkToSchoolSec'] = extractWalkToSchool($summary);
    usleep(80 * 1000);
}
file_put_contents('scored.json', json_encode($listings));
// .NET worker
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("LicenseKey", Environment.GetEnvironmentVariable("YATMO_KEY"));

var listings = JsonSerializer.Deserialize<List<Listing>>(
    await File.ReadAllTextAsync("listings.json"))!;

foreach (var l in listings) {
    var url = $"https://be.yatmo.com/summary?latitude={l.Lat}&longitude={l.Lng}&language=EN";
    var summary = await http.GetFromJsonAsync<JsonElement>(url);
    l.WalkToSchoolSec = ExtractWalkToSchool(summary);
    await Task.Delay(80);   // throttle
}
await File.WriteAllTextAsync("scored.json", JsonSerializer.Serialize(listings));

Production checklist