From 1b0969d5377f54514fd3e61f2eb7175c19079207 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:20:51 +0200 Subject: [PATCH] fix(stargazer-map): resolve countries via ISO code instead of English name (Russia/Turkey/Ivory Coast were dropped) (#2956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(stargazer-map): resolve countries via ISO code, not English name username_to_country() matched Nominatim's English display_name against pycountry, but the two vocabularies disagree: pycountry.countries.lookup() raises LookupError for "Russia", "Turkey" and "Ivory Coast" (its ISO names are "Russian Federation", "Türkiye", "Côte d'Ivoire"). Those users were silently recorded as unknown -- the committed cache has 963 users with a country and zero Russia, so Russia rendered grey on the map. Request addressdetails from Nominatim and read address.country_code instead. No new dependency, same one request per user, and it drops the reversed-component loop that could false-positive on a city or region named like a country. The return value is unchanged: still a pycountry .name string, so the CSV cache and the ISO-3 rendering lookup are unaffected. Co-Authored-By: Claude Opus 5 * fix(stargazer-map): drop non-answer locations before geocoding Co-Authored-By: Claude Opus 5 --------- Co-authored-by: Claude Opus 5 --- .github/generate_map.py | 50 +++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/.github/generate_map.py b/.github/generate_map.py index b5b1f4575e..d3e08ba712 100644 --- a/.github/generate_map.py +++ b/.github/generate_map.py @@ -61,6 +61,35 @@ HEADERS = { } GEOL = Nominatim(user_agent="gh-stargazer-map") +# Non-answers that Nominatim happily resolves to a real place: "Earth" is a +# town in Texas, "Remote" is a settlement in Oregon. Matched on the whole +# stripped, lowercased string only -- "Earth, TX" is someone's actual address +# and must still geocode. +JUNK_LOCATIONS = { + "127.0.0.1", + "/dev/null", + "anywhere", + "earth", + "everywhere", + "here", + "home", + "internet", + "localhost", + "mars", + "moon", + "n/a", + "none", + "nowhere", + "null", + "planet earth", + "remote", + "space", + "the internet", + "unknown", + "world", + "worldwide", +} + # ----------------------------------------------------------------------------- @@ -105,21 +134,18 @@ def username_to_country(login): loc = (resp.json() or {}).get("location") or "" if not loc.strip(): return "" + if loc.strip().strip(".!").lower() in JUNK_LOCATIONS: + return "" try: - g = GEOL.geocode(loc, language="en", timeout=10) + g = GEOL.geocode(loc, language="en", addressdetails=True, timeout=10) except Exception: return "" - if not g or "display_name" not in g.raw: - return "" - # take the last comma-separated component that matches a country - for part in reversed(g.raw["display_name"].split(",")): - part = part.strip() - try: - country = pycountry.countries.lookup(part).name - return country - except LookupError: - pass - return "" + # Use the ISO code from the structured address: Nominatim's English display + # names ("Russia", "Turkey", "Ivory Coast") do not all match pycountry's ISO + # names ("Russian Federation", "Türkiye", "Côte d'Ivoire"). + code = ((g.raw.get("address") or {}).get("country_code") or "") if g else "" + country = pycountry.countries.get(alpha_2=code.upper()) if code else None + return country.name if country else "" def count_by_country(cache):