Skip to main content
The application needs to turn a user’s latitude and longitude into a timezone (for panchangam and 6:30 AM scheduling) and into city/state/country (for display and caching). This is done by reverse geocoding: coordinates → place name and timezone. The codebase uses OpenCage as the primary source and a longitude-based fallback when OpenCage is unavailable or returns no result. Results are cached in the CityDetail table to reduce API calls and keep user records linked to a canonical place.

Overview

  • Primary: OpenCage Geocoding API — reverse geocode (lat, lon) to get timezone, city, state, country, and optional address/confidence.
  • Fallback: Longitude-based timezone estimation and simple location labels (no external API), in helpers/simple_timezone_fallback.py.
  • Caching: get_city_hybrid (and thus get_or_create_city) returns or creates a CityDetail row so the same (city, state, country) is reused and the user’s city_id points to it.
Required environment variable: OPENCAGE_API_KEY. Without it, the primary path raises when the geocoder is first used; the fallback is only used after an OpenCage call fails (e.g. network error or empty result).

Main functions

get_timezone_hybrid(lat, lon)

File: helpers/hybrid_geocoding.py Returns the IANA timezone name (e.g. America/New_York, Asia/Kolkata) for the given coordinates.
  1. OpenCage — Call reverse_geocode(lat, lon), read annotations.timezone.name from the first result. If present, return it and log.
  2. Fallback — If OpenCage fails or returns no timezone, call get_timezone_from_longitude(lat, lon) in simple_timezone_fallback.py, which maps longitude ranges to a fixed set of timezone names. If that fails, return "UTC".
Used when the app only needs a timezone (e.g. to decide scheduling or to pass into panchangam). The webhook location handler uses it first, then may overwrite with the city’s timezone when get_city_hybrid is used.

get_location_info_hybrid(lat, lon)

File: helpers/hybrid_geocoding.py Returns a dictionary with city, state, country, timezone, and metadata (no database write).
  1. OpenCage — Reverse geocode and parse components (city/town/village/municipality/suburb/county/state) and annotations.timezone. City is resolved with a long fallback chain (city → town → village → … → county → state → "Unknown Location"). Result includes source: 'opencage', confidence, formatted address.
  2. Fallback — If OpenCage fails, call get_simple_location_info(lat, lon) in simple_timezone_fallback.py, which uses get_timezone_from_longitude and coarse lat/lon rules (e.g. North America, India) to produce a minimal dict with city/state/country/timezone.
Used internally by get_city_hybrid; not typically called directly from the main app.

get_city_hybrid(lat, lon)

File: helpers/hybrid_geocoding.py Returns a CityDetail instance (existing or newly created) for the given coordinates.
  1. Call get_location_info_hybrid(lat, lon) to get city, state, country, timezone.
  2. Look up CityDetail by (city, state, country). If found, optionally update its timezone if it was missing or different, then return it.
  3. If not found, create a new CityDetail with city, state, country, timezone, latitude, longitude; commit and return.
This is the function that caches reverse-geocoding results in the database. The main app and admin portal use it when they need a CityDetail to attach to a user (e.g. user.city_id) and for display names in panchangam.

get_or_create_city(lat, lon)

File: helpers/city_utils.py Thin wrapper that delegates to get_city_hybrid(lat, lon). Exists for backward compatibility and clearer naming at call sites. Same behavior: returns a CityDetail or None if geocoding fails.

get_timezone_from_opencage(lat, lon)

File: helpers/city_utils.py Alias for get_timezone_hybrid (imported from hybrid_geocoding). Used by tests and any code that expects a “from OpenCage” name; behavior is the same hybrid (OpenCage first, then longitude fallback).

Longitude-based fallback

File: helpers/simple_timezone_fallback.py
  • get_timezone_from_longitude(lat, lon) — Splits the globe into longitude bands and returns a fixed IANA timezone per band (e.g. -90 to -67.5 → America/New_York, 60–75 → Asia/Kolkata). Latitude is not used. Intended as a last resort when APIs fail.
  • get_simple_location_info(lat, lon) — Returns a dict with city, state, country, timezone using the same timezone logic plus coarse regional rules (e.g. North America, India) to set generic labels. Used by get_location_info_hybrid when OpenCage fails.
This fallback avoids external calls but is approximate (no real place names beyond broad region, and no DST or political-boundary nuance).

Caching in CityDetail

The CityDetail model (see Database) stores:
  • city, state, country — Used as the unique logical key together (lookup in get_city_hybrid).
  • timezone — Can be updated when an existing row is found but had no timezone or a different one.
  • latitude, longitude — Stored on create; not used for lookup.
By using get_city_hybrid (or get_or_create_city) whenever the app needs a place for coordinates:
  • Repeated coordinates (or same city/state/country from OpenCage) reuse one row.
  • UserDetail rows reference it via city_id, so panchangam and admin can show a single canonical name and timezone per user.

Where it’s used in the codebase

  • app.pyLocation webhook: get_timezone_hybrid(lat, lon) for initial timezone; get_city_hybrid(lat, lon) for CityDetail, then user’s timezone is set from the city’s timezone if available, and user.city_id is set. Test route (e.g. generate-by-coords): get_city_hybrid for optional city display.
  • admin_portal/app.py — When an admin updates a user’s location (new lat/lon), get_timezone_hybrid and get_city_hybrid are used to resolve timezone and city and update the user record.
  • Tests — e.g. test_app_integration.py uses get_timezone_from_opencage and get_or_create_city to verify geocoding and DB caching.

Summary

GoalFunctionPrimary sourceFallback
Timezone onlyget_timezone_hybridOpenCage annotations.timezone.nameget_timezone_from_longitude → else UTC
Location dict (no DB)get_location_info_hybridOpenCage components + timezoneget_simple_location_info
City row (cached)get_city_hybrid / get_or_create_cityAbove + CityDetail lookup/insertSame as above; if both fail, returns None
All of this lives under helpers/ (hybrid_geocoding.py, city_utils.py, simple_timezone_fallback.py). The opencage Python package is used to call the OpenCage API; the key is configured via OPENCAGE_API_KEY.