Coverage for src/data_api/domain/locations/services.py: 96%

27 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-11-13 19:28 +0000

1""" 

2Locations domain service. 

3 

4Handles location/map data fetching and search. 

5""" 

6 

7from typing import Optional 

8 

9from thefuzz import fuzz 

10 

11from data_api.data.manager import nthudata 

12from data_api.data.nthudata import NTHUDataManager 

13 

14JSON_PATH = "maps.json" 

15FUZZY_SEARCH_THRESHOLD = 60 

16 

17 

18class LocationsService: 

19 """Service for location data operations.""" 

20 

21 def __init__(self, manager: NTHUDataManager = nthudata): 

22 self.manager = manager 

23 

24 async def get_all_locations(self) -> tuple[Optional[str], list[dict]]: 

25 """Get all locations.""" 

26 return await self.get_locations() 

27 

28 async def get_locations(self, name: str | None = None) -> tuple[Optional[str], list[dict]]: 

29 """Get locations with an optional exact name filter.""" 

30 commit_hash, map_data = await self.manager.get(JSON_PATH) 

31 locations = [ 

32 { 

33 "name": location_name, 

34 "latitude": coordinates["latitude"], 

35 "longitude": coordinates["longitude"], 

36 } 

37 for campus_locations in map_data.values() 

38 for location_name, coordinates in campus_locations.items() 

39 if not name or location_name == name 

40 ] 

41 return commit_hash, locations 

42 

43 async def fuzzy_search_locations(self, query: str) -> tuple[Optional[str], list[dict]]: 

44 """Fuzzy search locations by name.""" 

45 commit_hash, map_data = await self.manager.get(JSON_PATH) 

46 tmp_results = [] 

47 for campus_locations in map_data.values(): 

48 for location_name, coordinates in campus_locations.items(): 

49 similarity = fuzz.partial_ratio(query, location_name) 

50 if similarity >= FUZZY_SEARCH_THRESHOLD: 

51 location = { 

52 "name": location_name, 

53 "latitude": coordinates["latitude"], 

54 "longitude": coordinates["longitude"], 

55 } 

56 tmp_results.append((similarity, location)) 

57 

58 # Sort by exact match first, then by similarity 

59 tmp_results.sort(key=lambda x: (x[1]["name"] == query, x[0]), reverse=True) 

60 return commit_hash, [item[1] for item in tmp_results] 

61 

62 

63# Global service instance 

64locations_service = LocationsService()