avwx.station.search

Station text-based search

 1"""
 2Station text-based search
 3"""
 4
 5# stdlib
 6from contextlib import suppress
 7from functools import lru_cache
 8from typing import Iterable, List, Optional, Tuple
 9
10# module
11from avwx.load_utils import LazyCalc
12from avwx.station.meta import STATIONS
13from avwx.station.station import Station, station_filter
14
15
16# Catch import error only if user attemps a text search
17with suppress(ModuleNotFoundError):
18    from rapidfuzz import fuzz, process, utils
19
20
21TYPE_ORDER = [
22    "large_airport",
23    "medium_airport",
24    "small_airport",
25    "seaplane_base",
26    "heliport",
27    "balloonport",
28    "weather_station",
29]
30
31
32def _format_search(airport: dict, keys: Iterable[str]) -> Optional[str]:
33    values = [airport.get(k) for k in keys]
34    code = values[0] or values[2]
35    if not code:
36        return None
37    values.insert(0, code)
38    return " - ".join(k for k in values if k)
39
40
41def _build_corpus() -> List[str]:
42    keys = ("icao", "iata", "gps", "city", "state", "name")
43    return [text for s in STATIONS.values() if (text := _format_search(s, keys))]
44
45
46_CORPUS = LazyCalc(_build_corpus)
47
48
49def _sort_key(result: Tuple[Station, int]) -> Tuple[int, ...]:
50    station, score = result
51    try:
52        type_order = TYPE_ORDER.index(station.type)
53    except ValueError:
54        type_order = 10
55    return (score, 10 - type_order)
56
57
58@lru_cache(maxsize=128)
59def search(
60    text: str,
61    limit: int = 10,
62    is_airport: bool = False,
63    sends_reports: bool = True,
64) -> List[Station]:
65    """Text search for stations against codes, name, city, and state
66
67    Results may be shorter than limit value
68    """
69    try:
70        results = process.extract(
71            text,
72            _CORPUS.value,
73            limit=limit * 20,
74            scorer=fuzz.token_set_ratio,
75            processor=utils.default_process,
76        )
77    except NameError as name_error:
78        raise ModuleNotFoundError(
79            'rapidfuzz must be installed to use text search. Run "pip install avwx-engine[fuzz]" to enable this'
80        ) from name_error
81    results = [(Station.from_code(k[:4]), s) for k, s, _ in results]
82    results.sort(key=_sort_key, reverse=True)
83    results = [s for s, _ in results if station_filter(s, is_airport, sends_reports)]
84    return results[:limit] if len(results) > limit else results
TYPE_ORDER = ['large_airport', 'medium_airport', 'small_airport', 'seaplane_base', 'heliport', 'balloonport', 'weather_station']