From 6091a0acd8fc416b7675b2dd8348d03ff3612fc8 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Thu, 1 Oct 2026 14:22:02 +0700 Subject: [PATCH] fix(stats): tick the region-derivation counter live and merge name/code groups Two issues from the first derived-regions pass: - The 'Resolving N...' counter only updated when the whole pass finished, so it looked frozen. fillDerivedRegions now reports progress per pin, the hook ticks the counter down, and the card refreshes every 5 resolved pins so the numbers climb while you watch. - A country could appear twice ('United Kingdom' 1 + 1) when one pin carried a country_code and another only a name. Learn name->code from pins that have both and fold name-only pins into the same bucket. --- src/hooks/useDeriveRegions.ts | 13 +++++++++++-- src/hooks/useGrowthStats.ts | 12 +++++++++++- src/lib/derivedRegions.ts | 23 +++++++++++++++-------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/hooks/useDeriveRegions.ts b/src/hooks/useDeriveRegions.ts index 7c8ea91..d7fbcae 100644 --- a/src/hooks/useDeriveRegions.ts +++ b/src/hooks/useDeriveRegions.ts @@ -64,11 +64,20 @@ export function useDeriveRegions(): DeriveRegionsState { running.current = true; void (async () => { + let lastRefresh = 0; try { - await fillDerivedRegions(misses); + await fillDerivedRegions(misses, (done, total) => { + setPending(Math.max(0, total - done)); + // Refresh the card as pins resolve (not just at the end) so the numbers + // climb while you watch, instead of jumping once. + if (done - lastRefresh >= 5) { + lastRefresh = done; + queryClient.invalidateQueries({ queryKey: ['growth-stats'] }); + } + }); const unresolved = misses.filter((c) => !getDerivedRegion(c.lat, c.lng)).length; setPending(unresolved); - // Recompute the region card now that more pins have a derived region. + // Final recompute now that every pin has (or has failed to get) a region. queryClient.invalidateQueries({ queryKey: ['growth-stats'] }); } finally { running.current = false; diff --git a/src/hooks/useGrowthStats.ts b/src/hooks/useGrowthStats.ts index 3b21c4a..830aa73 100644 --- a/src/hooks/useGrowthStats.ts +++ b/src/hooks/useGrowthStats.ts @@ -315,13 +315,23 @@ export function useGrowthStats() { return null; }; const regionEntries = allLocations.map((e) => regionOf(e)); + // A country can arrive WITH a code (tagged pins) or WITHOUT one (some + // geocodes). Learn name→code from whichever pins carry both, so e.g. + // "United Kingdom" with no code folds into the "GB" bucket instead of + // splitting into two identical rows. + const codeByName = new Map(); + for (const region of regionEntries) { + if (region?.country && region.code) codeByName.set(region.country.toLowerCase(), region.code); + } const regionGroups = (kind: 'country' | 'city') => { const groups = new Map }>(); for (const region of regionEntries) { if (!region) continue; const name = kind === 'country' ? region.country : region.city; if (!name) continue; - const key = kind === 'country' ? region.code || name.toLowerCase() : name.toLowerCase(); + const key = kind === 'country' + ? region.code || codeByName.get(name.toLowerCase()) || name.toLowerCase() + : name.toLowerCase(); const g = groups.get(key) ?? { total: 0, labels: new Map() }; g.total += 1; g.labels.set(name, (g.labels.get(name) ?? 0) + 1); diff --git a/src/lib/derivedRegions.ts b/src/lib/derivedRegions.ts index 8a7dc31..4466630 100644 --- a/src/lib/derivedRegions.ts +++ b/src/lib/derivedRegions.ts @@ -93,19 +93,26 @@ export function coordsOf(event: { tags: string[][] }): { lat: number; lng: numbe */ export async function fillDerivedRegions( coords: Array<{ lat: number; lng: number }>, + onProgress?: (done: number, total: number) => void, ): Promise { let filled = 0; + const seen = new Set(); for (let i = 0; i < coords.length; i += 1) { const { lat, lng } = coords[i]; - if (getDerivedRegion(lat, lng)) continue; - const geo = await reverseGeocodeRegions(lat, lng); - if (geo.city || geo.country || geo.countryCode) { - setDerivedRegion(lat, lng, { city: geo.city, country: geo.country, countryCode: geo.countryCode }); - filled += 1; - } - if (i < coords.length - 1) { - await new Promise((resolve) => setTimeout(resolve, GEOCODE_SPACING_MS)); + const key = regionCoordKey(lat, lng); + // Skip duplicates / already-cached pins, but still advance the progress bar. + if (!seen.has(key) && !getDerivedRegion(lat, lng)) { + seen.add(key); + const geo = await reverseGeocodeRegions(lat, lng); + if (geo.city || geo.country || geo.countryCode) { + setDerivedRegion(lat, lng, { city: geo.city, country: geo.country, countryCode: geo.countryCode }); + filled += 1; + } + if (i < coords.length - 1) { + await new Promise((resolve) => setTimeout(resolve, GEOCODE_SPACING_MS)); + } } + onProgress?.(i + 1, coords.length); } return filled; }