File size: 8,232 Bytes
b0979b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
"""
Central API configuration and environment setup.
"""
import os
import requests
from typing import Dict, Tuple, Optional
import json
import time
from urllib.parse import quote

class APIConfig:
    """Centralized API configuration and validation."""
    
    def __init__(self):
        self.foursquare_api_key = os.environ.get('FOURSQUARE_API_KEY')
        self.anthropic_api_key = os.environ.get('ANTHROPIC_API_KEY')
        
        # Set environment if not already set and key is available
        if self.foursquare_api_key and not os.environ.get('FOURSQUARE_API_KEY'):
            os.environ['FOURSQUARE_API_KEY'] = self.foursquare_api_key
    
    def validate_apis(self) -> Dict[str, bool]:
        """Validate API keys and connectivity."""
        results = {}
        
        # Test Foursquare API
        if self.foursquare_api_key:
            try:
                url = "https://api.foursquare.com/v3/places/search"
                headers = {
                    "accept": "application/json",
                    "Authorization": self.foursquare_api_key
                }
                params = {"query": "test", "limit": 1}
                response = requests.get(url, headers=headers, params=params, timeout=5)
                results['foursquare'] = response.status_code == 200
            except Exception:
                results['foursquare'] = False
        else:
            results['foursquare'] = False
        
        # Check Anthropic API key existence
        results['anthropic'] = bool(self.anthropic_api_key)
        
        return results
    
    def print_status(self):
        """Print API configuration status."""
        print("\n🔧 API Configuration Status:")
        
        validation = self.validate_apis()
        
        if validation['foursquare']:
            print("✓ Foursquare API: Connected and working")
        else:
            print("⚠️ Foursquare API: Connection issues or invalid key")
        
        if validation['anthropic']:
            print("✓ Anthropic API Key: Configured")
        else:
            print("⚠️ Anthropic API Key: Not configured")
            print("ℹ️ Set environment variable: ANTHROPIC_API_KEY=your_key")

class DynamicCoordinateService:
    """Service for dynamically fetching coordinates for locations."""
    
    def __init__(self):
        self.coordinate_cache = {}  # Cache for recently fetched coordinates
        self.cache_expiry = 3600    # Cache expires after 1 hour
        self.foursquare_api_key = os.environ.get('FOURSQUARE_API_KEY')
    
    def get_coordinates(self, location: str) -> Optional[str]:
        """
        Get coordinates for a location dynamically using multiple geocoding services.
        Raises ValueError if coordinates cannot be found.
        """
        location_key = location.lower().strip()
        
        # Check cache first
        if location_key in self.coordinate_cache:
            cached_data = self.coordinate_cache[location_key]
            if time.time() - cached_data['timestamp'] < self.cache_expiry:
                return cached_data['coordinates']
        
        # Try multiple geocoding services in order of preference
        coordinates = None
        
        # 1. Try Foursquare API first (most accurate for places)
        if self.foursquare_api_key:
            coordinates = self._get_coordinates_foursquare(location)
        
        # 2. Fall back to Nominatim (OpenStreetMap) - free but rate limited
        if not coordinates:
            coordinates = self._get_coordinates_nominatim(location)
        
        # 3. If all services fail, raise an error
        if not coordinates:
            raise ValueError(f"Could not retrieve coordinates for '{location}'. Please try a more specific location or check the spelling.")
        
        # Cache the successful result
        self.coordinate_cache[location_key] = {
            'coordinates': coordinates,
            'timestamp': time.time()
        }
        
        return coordinates
    
    def _get_coordinates_foursquare(self, location: str) -> Optional[str]:
        """Get coordinates using Foursquare Places API."""
        try:
            url = "https://api.foursquare.com/v3/places/search"
            headers = {
                "accept": "application/json",
                "Authorization": self.foursquare_api_key
            }
            params = {
                "query": location,
                "limit": 1
            }
            
            response = requests.get(url, headers=headers, params=params, timeout=10)
            response.raise_for_status()
            
            data = response.json()
            if data.get('results'):
                geocode = data['results'][0].get('geocodes', {}).get('main', {})
                if 'latitude' in geocode and 'longitude' in geocode:
                    lat = geocode['latitude']
                    lon = geocode['longitude']
                    return f"{lat},{lon}"
                    
        except Exception as e:
            print(f"Foursquare geocoding failed for '{location}': {e}")
        
        return None
    
    def _get_coordinates_nominatim(self, location: str) -> Optional[str]:
        """Get coordinates using Nominatim (OpenStreetMap) geocoding service."""
        try:
            # Rate limit: max 1 request per second for Nominatim
            time.sleep(1)
            
            encoded_location = quote(location)
            url = f"https://nominatim.openstreetmap.org/search"
            params = {
                'q': location,
                'format': 'json',
                'limit': 1,
                'addressdetails': 1
            }
            headers = {
                'User-Agent': 'MCP-Server/1.0 (Dynamic Coordinate Service)'
            }
            
            response = requests.get(url, params=params, headers=headers, timeout=10)
            response.raise_for_status()
            
            data = response.json()
            if data:
                lat = data[0]['lat']
                lon = data[0]['lon']
                return f"{lat},{lon}"
                
        except Exception as e:
            print(f"Nominatim geocoding failed for '{location}': {e}")
        
        return None
    
    def get_coordinates_tuple(self, location: str) -> Optional[Tuple[float, float]]:
        """Get coordinates as a tuple of (latitude, longitude). Raises ValueError if not found."""
        coords_str = self.get_coordinates(location)
        if coords_str:
            try:
                lat, lon = coords_str.split(',')
                return (float(lat), float(lon))
            except (ValueError, IndexError):
                raise ValueError(f"Invalid coordinate format for '{location}'")
        return None
    
    def clear_cache(self):
        """Clear the coordinate cache."""
        self.coordinate_cache.clear()

# Global configuration instance
api_config = APIConfig()

# Dynamic coordinate service instance
coordinate_service = DynamicCoordinateService()

# Legacy function for backward compatibility
def get_city_coordinates(location: str) -> Optional[str]:
    """
    Get coordinates for a location dynamically.
    This replaces the old static CITY_COORDINATES lookup.
    """
    return coordinate_service.get_coordinates(location)

# Backward compatibility: Minimal CITY_COORDINATES for legacy code
# This is deprecated - use coordinate_service.get_coordinates() instead
CITY_COORDINATES = {
    # Common US cities for backward compatibility only
    'seattle': '47.6062,-122.3321',
    'portland': '45.5155,-122.6789',
    'san francisco': '37.7749,-122.4194',
    'los angeles': '34.0522,-118.2437',
    'denver': '39.7392,-104.9903',
    'chicago': '41.8781,-87.6298',
    'new york': '40.7128,-74.0060',
    'boston': '42.3601,-71.0589',
    'miami': '25.7617,-80.1918',
    'austin': '30.2672,-97.7431',
    'phoenix': '33.4484,-112.0740',
    'atlanta': '33.7490,-84.3880',
    'nashville': '36.1627,-86.7816',
    'minneapolis': '44.9778,-93.2650',
    'lynnwood': '47.8209,-122.3151'
}

# Note: This static mapping is deprecated. 
# For new code, use: coordinate_service.get_coordinates(location) for dynamic lookup