"""Access to the curated city set (backend/cities.json) that gets crawlable climate pages. Loaded once, lazily; regenerate the JSON with gen_cities.py.""" import json import os _PATH = os.path.join(os.path.dirname(__file__), "cities.json") _FLAVOR_PATH = os.path.join(os.path.dirname(__file__), "cities_flavor.json") _CITIES: list[dict] | None = None _BY_SLUG: dict[str, dict] | None = None _FLAVOR: dict[str, dict] | None = None def _load() -> list[dict]: global _CITIES, _BY_SLUG if _CITIES is None: with open(_PATH, encoding="utf-8") as f: _CITIES = json.load(f) _BY_SLUG = {c["slug"]: c for c in _CITIES} return _CITIES def all_cities() -> list[dict]: return _load() def all_slugs() -> list[str]: return [c["slug"] for c in _load()] def get(slug: str) -> dict | None: """The city for a slug, or None (→ 404).""" _load() return _BY_SLUG.get(slug) def flavor(slug: str) -> dict | None: """A city's descriptive blurb {extract, url, title} from cities_flavor.json, or None when we have no confident match (the page renders fine without it).""" global _FLAVOR if _FLAVOR is None: try: with open(_FLAVOR_PATH, encoding="utf-8") as f: _FLAVOR = json.load(f) except (OSError, ValueError): _FLAVOR = {} return _FLAVOR.get(slug) def display_name(city: dict) -> str: """Human label: 'Seattle, Washington, United States' (drops repeated admin1).""" parts = [city["name"]] if city.get("admin1") and city["admin1"] != city["name"]: parts.append(city["admin1"]) if city.get("country"): parts.append(city["country"]) return ", ".join(parts) def by_country() -> dict[str, list[dict]]: """Cities grouped by country, both the countries and the cities within each ordered alphabetically — for the /climate hub's crawlable link graph + search.""" groups: dict[str, list[dict]] = {} for c in _load(): key = c.get("country") or c.get("country_code") or "Other" groups.setdefault(key, []).append(c) for v in groups.values(): v.sort(key=lambda x: x["name"].lower()) return dict(sorted(groups.items(), key=lambda kv: kv[0].lower()))