|
1 | 1 | """Utilities for the AccuWeather backend."""
|
2 | 2 |
|
3 | 3 | import logging
|
4 |
| -import math |
5 | 4 | from enum import StrEnum
|
6 | 5 | from typing import Any, TypedDict
|
7 | 6 |
|
8 | 7 | from httpx import URL, InvalidURL
|
9 | 8 | from merino.configs import settings
|
10 |
| -from merino.providers.suggest.weather.backends.protocol import WeatherContext |
11 | 9 |
|
12 | 10 | PARTNER_PARAM_ID: str | None = settings.accuweather.get("url_param_partner_code")
|
13 | 11 | PARTNER_CODE: str | None = settings.accuweather.get("partner_code")
|
14 | 12 | VALID_LANGUAGES: frozenset = frozenset(settings.accuweather.default_languages)
|
15 | 13 |
|
16 |
| -# Max distance threshold used for distance calculation |
17 |
| -DISTANCE_THRESHOLD = ( |
18 |
| - 70 # 70 km based on logging data, either distances were ~60, or much greater than 60 |
19 |
| -) |
20 |
| - |
21 | 14 | logger = logging.getLogger(__name__)
|
22 | 15 |
|
23 | 16 |
|
@@ -82,7 +75,7 @@ def process_location_completion_response(response: Any) -> list[dict[str, Any]]
|
82 | 75 | ]
|
83 | 76 |
|
84 | 77 |
|
85 |
| -def process_location_response_with_country_and_region( |
| 78 | +def process_location_response( |
86 | 79 | response: Any,
|
87 | 80 | ) -> ProcessedLocationResponse | None:
|
88 | 81 | """Process the API response for location keys.
|
@@ -112,35 +105,6 @@ def process_location_response_with_country_and_region(
|
112 | 105 | return None
|
113 | 106 |
|
114 | 107 |
|
115 |
| -def process_location_response_with_country( |
116 |
| - weather_context: WeatherContext | None, response: Any |
117 |
| -) -> ProcessedLocationResponse | None: |
118 |
| - """Process the API response for a single location key from country code endpoint. |
119 |
| -
|
120 |
| - Note that if you change the return format, ensure you update `LUA_SCRIPT_CACHE_BULK_FETCH` |
121 |
| - to reflect the change(s) here. |
122 |
| - """ |
123 |
| - match response: |
124 |
| - case [ |
125 |
| - { |
126 |
| - "Key": key, |
127 |
| - "LocalizedName": localized_name, |
128 |
| - "AdministrativeArea": {"ID": administrative_area_id}, |
129 |
| - }, |
130 |
| - ]: |
131 |
| - # `type: ignore` is necessary because mypy gets confused when |
132 |
| - # matching structures of type `Any` and reports the following |
133 |
| - # line as unreachable. See |
134 |
| - # https://github.com/python/mypy/issues/12770 |
135 |
| - return { # type: ignore |
136 |
| - "key": key, |
137 |
| - "localized_name": localized_name, |
138 |
| - "administrative_area_id": administrative_area_id, |
139 |
| - } |
140 |
| - case _: |
141 |
| - return get_closest_location_by_distance(response, weather_context) # type: ignore |
142 |
| - |
143 |
| - |
144 | 108 | def process_current_condition_response(response: Any) -> dict[str, Any] | None:
|
145 | 109 | """Process the API response for current conditions."""
|
146 | 110 | match response:
|
@@ -218,75 +182,3 @@ def get_language(requested_languages: list[str]) -> str:
|
218 | 182 | return next(
|
219 | 183 | (language for language in requested_languages if language in VALID_LANGUAGES), "en-US"
|
220 | 184 | )
|
221 |
| - |
222 |
| - |
223 |
| -def get_closest_location_by_distance( |
224 |
| - locations: list[dict[str, Any]], weather_context: WeatherContext |
225 |
| -) -> ProcessedLocationResponse | None: |
226 |
| - """Get the closest location by distance within the DISTANCE THRESHOLD.""" |
227 |
| - weather_context.distance_calculation = False if locations else None |
228 |
| - coordinates = weather_context.geolocation.coordinates |
229 |
| - closest_location = None |
230 |
| - min_distance = math.inf |
231 |
| - temp_min_distance = math.inf |
232 |
| - |
233 |
| - if coordinates: |
234 |
| - lat1 = coordinates.latitude |
235 |
| - long1 = coordinates.longitude |
236 |
| - |
237 |
| - if not lat1 or not long1: |
238 |
| - return None |
239 |
| - |
240 |
| - for location in locations: |
241 |
| - try: |
242 |
| - lat2 = location["GeoPosition"]["Latitude"] |
243 |
| - long2 = location["GeoPosition"]["Longitude"] |
244 |
| - |
245 |
| - d = get_lat_long_distance(lat1, long1, lat2, long2) |
246 |
| - if d < min_distance and d <= DISTANCE_THRESHOLD: |
247 |
| - closest_location = location |
248 |
| - min_distance = d |
249 |
| - if d < temp_min_distance: |
250 |
| - temp_min_distance = d |
251 |
| - except KeyError: |
252 |
| - continue |
253 |
| - |
254 |
| - if closest_location: |
255 |
| - try: |
256 |
| - weather_context.distance_calculation = True |
257 |
| - logging.info( |
258 |
| - f"Successful distance calculated for weather: {weather_context.geolocation.country}, {weather_context.geolocation.city}, dist:{min_distance} " |
259 |
| - ) |
260 |
| - return { |
261 |
| - "key": closest_location["Key"], |
262 |
| - "localized_name": closest_location["LocalizedName"], |
263 |
| - "administrative_area_id": closest_location["AdministrativeArea"]["ID"], |
264 |
| - } |
265 |
| - except KeyError: |
266 |
| - weather_context.distance_calculation = False |
267 |
| - return None |
268 |
| - # temp for debugging |
269 |
| - if not math.isinf(temp_min_distance): |
270 |
| - logger.warning( |
271 |
| - f"Unable to calculate closest city: {weather_context.geolocation.country}, {weather_context.geolocation.city}, dist: {int(temp_min_distance)}" |
272 |
| - ) |
273 |
| - return None |
274 |
| - |
275 |
| - |
276 |
| -def get_lat_long_distance(lat1: float, long1: float, lat2: float, long2: float) -> float: |
277 |
| - """Calculate distance between two coordinates via the Haversine formula.""" |
278 |
| - lat1 = math.radians(lat1) |
279 |
| - long1 = math.radians(long1) |
280 |
| - |
281 |
| - lat2 = math.radians(lat2) |
282 |
| - long2 = math.radians(long2) |
283 |
| - |
284 |
| - d_lat = lat2 - lat1 |
285 |
| - d_long = long2 - long1 |
286 |
| - |
287 |
| - radicand = 1 - math.cos(d_lat) + (math.cos(lat1) * math.cos(lat2) * (1 - math.cos(d_long))) |
288 |
| - s9rt = math.sqrt(radicand / 2) |
289 |
| - # radius of the earth |
290 |
| - r = 6371 |
291 |
| - |
292 |
| - return 2 * r * math.asin(s9rt) |
0 commit comments