Spaces:
Running
Running
| """ | |
| RewardPilot - City & platform availability | |
| =========================================== | |
| Answers two questions the channel comparison needs: | |
| 1. Which city is the user in? (explicit city > city named in the query > | |
| nearest metro centroid from GPS coordinates) | |
| 2. Is a booking platform - and, when we know the chain, this specific | |
| merchant - actually available in that city? | |
| Curated seed, same philosophy as offers.py: structured and dated in spirit, | |
| refreshed by the ingestion pipeline (connectors/) later. Coverage values are | |
| illustrative until a live feed is wired. | |
| Availability levels: | |
| "available" - platform (and merchant, when known) confirmed in this city | |
| "unknown" - no data to confirm; channel is kept but flagged unverified | |
| "unavailable" - confirmed absent (platform not in city, or chain not listed | |
| on this platform); channel is dropped from the comparison | |
| Mirrored on-device in app/src/data/availability.ts. | |
| """ | |
| import math | |
| from typing import Dict, Optional, Set, Union | |
| # --------------------------------------------------------------------------- | |
| # Cities: canonical name -> (lat, lng) centroid. Used for GPS fallback. | |
| # --------------------------------------------------------------------------- | |
| CITIES: Dict[str, tuple] = { | |
| "mumbai": (19.0760, 72.8777), | |
| "delhi": (28.6139, 77.2090), | |
| "bengaluru": (12.9716, 77.5946), | |
| "hyderabad": (17.3850, 78.4867), | |
| "chennai": (13.0827, 80.2707), | |
| "kolkata": (22.5726, 88.3639), | |
| "pune": (18.5204, 73.8567), | |
| "ahmedabad": (23.0225, 72.5714), | |
| "jaipur": (26.9124, 75.7873), | |
| "surat": (21.1702, 72.8311), | |
| "lucknow": (26.8467, 80.9462), | |
| "chandigarh": (30.7333, 76.7794), | |
| "indore": (22.7196, 75.8577), | |
| "kochi": (9.9312, 76.2673), | |
| "goa": (15.4909, 73.8278), | |
| } | |
| # Common alternates / NCR satellites -> canonical city key. | |
| CITY_SYNONYMS: Dict[str, str] = { | |
| "bombay": "mumbai", "navi mumbai": "mumbai", "thane": "mumbai", | |
| "new delhi": "delhi", "gurgaon": "delhi", "gurugram": "delhi", | |
| "noida": "delhi", "ghaziabad": "delhi", "faridabad": "delhi", | |
| "bangalore": "bengaluru", | |
| "madras": "chennai", | |
| "calcutta": "kolkata", | |
| "cochin": "kochi", | |
| "secunderabad": "hyderabad", | |
| "panaji": "goa", "panjim": "goa", | |
| } | |
| def normalize_city(name: Optional[str]) -> Optional[str]: | |
| """Free-text city name -> canonical key, or None if we don't know it.""" | |
| t = (name or "").strip().lower() | |
| if not t: | |
| return None | |
| if t in CITIES: | |
| return t | |
| return CITY_SYNONYMS.get(t) | |
| def extract_city(text: str) -> Optional[str]: | |
| """Find a city mentioned inside a free-text query ("dinner ... in Mumbai").""" | |
| t = (text or "").lower() | |
| hits = [k for k in list(CITIES) + list(CITY_SYNONYMS) if k in t] | |
| if not hits: | |
| return None | |
| pick = sorted(hits, key=len, reverse=True)[0] | |
| return pick if pick in CITIES else CITY_SYNONYMS[pick] | |
| def city_from_latlng(lat: float, lng: float, max_km: float = 60.0) -> Optional[str]: | |
| """Nearest metro centroid within max_km, else None.""" | |
| best, best_d = None, max_km | |
| for city, (clat, clng) in CITIES.items(): | |
| p1, p2 = math.radians(lat), math.radians(clat) | |
| dp, dl = math.radians(clat - lat), math.radians(clng - lng) | |
| h = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2 | |
| d = 2 * 6371.0 * math.asin(min(1.0, math.sqrt(h))) | |
| if d < best_d: | |
| best, best_d = city, d | |
| return best | |
| # --------------------------------------------------------------------------- | |
| # Platform coverage: (channel_key, kind) -> "*" (pan-India) or a city set. | |
| # Direct/walk-in channels and travel OTAs are always available. | |
| # --------------------------------------------------------------------------- | |
| _ALL = "*" | |
| PLATFORM_CITIES: Dict[tuple, Union[str, Set[str]]] = { | |
| ("dineout", "dining"): { | |
| "mumbai", "delhi", "bengaluru", "hyderabad", "chennai", "kolkata", | |
| "pune", "ahmedabad", "jaipur", "lucknow", "chandigarh", "indore", | |
| }, | |
| ("district", "dining"): { | |
| "mumbai", "delhi", "bengaluru", "hyderabad", "chennai", "kolkata", | |
| "pune", "ahmedabad", | |
| }, | |
| ("district", "movies"): { | |
| "mumbai", "delhi", "bengaluru", "hyderabad", "chennai", "pune", | |
| }, | |
| ("bookmyshow", "movies"): _ALL, | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Merchant-level listings (dining chains we track): brand_key -> platform -> | |
| # "*" or city set. A chain present in this dict with a platform MISSING means | |
| # we know it is NOT listed there (e.g. QSR counter chains aren't on the | |
| # dine-in bill-payment platforms). | |
| # --------------------------------------------------------------------------- | |
| MERCHANT_PLATFORMS: Dict[str, Dict[str, Union[str, Set[str]]]] = { | |
| "copper_chimney": { | |
| "dineout": {"mumbai", "delhi", "pune", "bengaluru"}, | |
| "district": {"mumbai", "pune", "bengaluru"}, | |
| }, | |
| "barbeque_nation": {"dineout": _ALL, "district": _ALL}, | |
| # counter/QSR chains: you pay at the till, not through a dine-in bill app | |
| "starbucks": {}, | |
| "haldiram": {}, | |
| "dominos": {}, | |
| "pizza_hut": {}, | |
| "mcdonalds": {}, | |
| "kfc": {}, | |
| } | |
| def channel_availability( | |
| channel_key: str, | |
| kind: str, | |
| city: Optional[str], | |
| brand_key: Optional[str] = None, | |
| ) -> str: | |
| """'available' | 'unknown' | 'unavailable' for one channel in one city.""" | |
| if channel_key == "direct": | |
| return "available" | |
| if kind in ("flights", "hotels"): | |
| return "available" # travel OTAs are pan-India | |
| # Merchant-level check first (dining chains we track). | |
| if kind == "dining" and brand_key in MERCHANT_PLATFORMS: | |
| listed = MERCHANT_PLATFORMS[brand_key] | |
| cities = listed.get(channel_key) | |
| if cities is None: | |
| return "unavailable" # chain known, not listed on this platform | |
| if cities != _ALL: | |
| if city is None: | |
| return "unknown" # listed somewhere, can't confirm this city | |
| return "available" if city in cities else "unavailable" | |
| # cities == "*": fall through to the platform's own city coverage | |
| cov = PLATFORM_CITIES.get((channel_key, kind)) | |
| if cov is None: | |
| return "unknown" | |
| if cov == _ALL: | |
| return "available" | |
| if city is None: | |
| return "unknown" | |
| return "available" if city in cov else "unavailable" | |