fix(stargazer-map): resolve countries via ISO code instead of English name (Russia/Turkey/Ivory Coast were dropped) (#2956)

* 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 <noreply@anthropic.com>

* fix(stargazer-map): drop non-answer locations before geocoding

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Alexandre
2026-08-10 16:20:51 +02:00
committed by GitHub
parent a830293736
commit 1b0969d537

View File

@@ -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):