Merge modifications for more separate backend functions #69
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -4,7 +4,6 @@ import yaml | ||||
|  | ||||
| from ..structs.preferences import Preferences | ||||
| from ..structs.landmark import Landmark | ||||
| from ..utils.take_most_important import take_most_important | ||||
| from .cluster_manager import ClusterManager | ||||
| from ..overpass.overpass import Overpass, get_base_info | ||||
| from ..utils.bbox import create_bbox | ||||
| @@ -23,7 +22,7 @@ class LandmarkManager: | ||||
|     church_coeff: float     # coeff to adjsut score of churches | ||||
|     nature_coeff: float       # coeff to adjust score of parks | ||||
|     overall_coeff: float        # coeff to adjust weight of tags | ||||
|     n_important: int        # number of important landmarks to consider | ||||
|     # n_important: int        # number of important landmarks to consider | ||||
| 
					
					kscheidecker marked this conversation as resolved
					
						
						
							Outdated
						
					
				 | ||||
|  | ||||
|  | ||||
|     def __init__(self) -> None: | ||||
| @@ -42,7 +41,7 @@ class LandmarkManager: | ||||
|             self.wikipedia_bonus = parameters['wikipedia_bonus'] | ||||
|             self.viewpoint_bonus = parameters['viewpoint_bonus'] | ||||
|             self.pay_bonus = parameters['pay_bonus'] | ||||
|             self.n_important = parameters['N_important'] | ||||
|             # self.n_important = parameters['N_important'] | ||||
|  | ||||
|         with OPTIMIZER_PARAMETERS_PATH.open('r') as f: | ||||
|             parameters = yaml.safe_load(f) | ||||
| @@ -60,7 +59,7 @@ class LandmarkManager: | ||||
|             center_coordinates: tuple[float, float], | ||||
|             preferences: Preferences, | ||||
|             allow_clusters: bool = True | ||||
|             ) -> tuple[list[Landmark], list[Landmark]]: | ||||
|             ) -> list[Landmark] : | ||||
|         """ | ||||
|         Generate and prioritize a list of landmarks based on user preferences. | ||||
|  | ||||
| @@ -127,13 +126,12 @@ class LandmarkManager: | ||||
|                 all_landmarks.update(shopping_clusters) | ||||
|  | ||||
|  | ||||
|         landmarks_constrained = take_most_important(all_landmarks, self.n_important) | ||||
|  | ||||
|         # DETAILS HERE | ||||
|         # self.logger.info(f'All landmarks generated : {len(all_landmarks)} landmarks around {center_coordinates}, and constrained to {len(landmarks_constrained)} most important ones.') | ||||
|         self.logger.info(f'Found {len(all_landmarks)} landmarks in total.') | ||||
|  | ||||
|         return all_landmarks, landmarks_constrained | ||||
|         return sorted(all_landmarks, key=lambda x: x.attractiveness, reverse=True) | ||||
|      | ||||
|  | ||||
|     def set_landmark_score(self, landmark: Landmark, landmarktype: str, preference_level: int) : | ||||
|         """ | ||||
|   | ||||
| @@ -6,7 +6,7 @@ from fastapi import HTTPException, APIRouter | ||||
| 
 | ||||
| from ..structs.landmark import Landmark | ||||
| from ..structs.preferences import Preferences, Preference | ||||
| from ..landmarks.landmarks_manager import LandmarkManager | ||||
| from .landmarks_manager import LandmarkManager | ||||
| 
 | ||||
| 
 | ||||
| # Setup the logger and the Landmarks Manager | ||||
| @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) | ||||
| manager = LandmarkManager() | ||||
| 
 | ||||
| 
 | ||||
| # Start the router | ||||
| # Initialize the API router | ||||
| router = APIRouter() | ||||
| 
 | ||||
| 
 | ||||
| @@ -48,7 +48,7 @@ def get_landmarks( | ||||
|     start_time = time.time() | ||||
| 
 | ||||
|     # Generate the landmarks from the start location | ||||
|     landmarks, _ = manager.generate_landmarks_list( | ||||
|     landmarks = manager.generate_landmarks_list( | ||||
|         center_coordinates = start, | ||||
|         preferences = preferences | ||||
|     ) | ||||
| @@ -62,7 +62,7 @@ def get_landmarks( | ||||
|     return landmarks | ||||
| 
 | ||||
| 
 | ||||
| @router.post("/landmarks/get-nearby/{lat}/{lon}") | ||||
| @router.post("/get-nearby/landmarks/{lat}/{lon}") | ||||
| def get_landmarks_nearby( | ||||
|     lat: float, | ||||
|     lon: float | ||||
| @@ -81,6 +81,7 @@ def get_landmarks_nearby( | ||||
|         list[Landmark]: A list of selected nearby landmarks. | ||||
|     """ | ||||
|     logger.info(f'Fetching landmarks nearby ({lat}, {lon}).') | ||||
| 
 | ||||
|     # Define fixed preferences: | ||||
|     prefs = Preferences( | ||||
|         sightseeing = Preference( | ||||
| @@ -100,18 +101,23 @@ def get_landmarks_nearby( | ||||
|     ) | ||||
| 
 | ||||
|     # Find the landmarks around the location | ||||
|     _, landmarks_around = manager.generate_landmarks_list( | ||||
|     landmarks_around = manager.generate_landmarks_list( | ||||
|         center_coordinates = (lat, lon), | ||||
|         preferences = prefs, | ||||
|         allow_clusters=False, | ||||
| 
					
					kscheidecker marked this conversation as resolved
					
				 
				
					
						remoll
						commented  should this be a landmark-parameter? No need to let the user choose that option. But it might be intersting to allow this to be set at runtime instead of hardcoding it. should this be a landmark-parameter? No need to let the user choose that option. But it might be intersting to allow this to be set at runtime instead of hardcoding it. 
				
					
						kscheidecker
						commented  Also a good point! Implemented in commit bfc0c9adae Also a good point! Implemented in commit [bfc0c9adae](https://git.kluster.moll.re/anydev/anyway/commit/bfc0c9adae3877d59f4127e94e2a4916d9072a7b) | ||||
|     ) | ||||
| 
 | ||||
|     if len(landmarks_around) == 0 : | ||||
|         raise HTTPException(status_code=500, detail="No landmarks were found.") | ||||
| 
 | ||||
|     # select 5 landmarks from there | ||||
|     if len(landmarks_around) > 6 : | ||||
|         landmarks_around = landmarks_around[:2] + random.sample(landmarks_around[3:], 2) | ||||
|     # select 8 - 12 landmarks from there | ||||
|     if len(landmarks_around) > 8 : | ||||
|         n_imp = random.randint(2,5) | ||||
|         rest = random.randint(8 - n_imp, min(12, len(landmarks_around))-n_imp) | ||||
| 
 | ||||
|         print(f'len = {len(landmarks_around)}\nn_imp = {n_imp}\nrest = {rest}') | ||||
|         landmarks_around = landmarks_around[:n_imp] + random.sample(landmarks_around[n_imp:], rest) | ||||
|      | ||||
|     logger.info(f'Found {len(landmarks_around)} landmarks nearby ({lat}, {lon}).') | ||||
|     logger.debug('Suggested landmarks :\n\t' + '\n\t'.join(f'{landmark}' for landmark in landmarks_around)) | ||||
|     logger.info(f'Found {len(landmarks_around)} landmarks to suggest nearby ({lat}, {lon}).') | ||||
|     # logger.debug('Suggested landmarks :\n\t' + '\n\t'.join(f'{landmark}' for landmark in landmarks_around)) | ||||
|     return landmarks_around | ||||
| @@ -1,22 +1,18 @@ | ||||
| """Main app for backend api""" | ||||
| import logging | ||||
| import time | ||||
| import random | ||||
| from contextlib import asynccontextmanager | ||||
| from fastapi import FastAPI, HTTPException, BackgroundTasks | ||||
| from fastapi import FastAPI, HTTPException | ||||
|  | ||||
| from .logging_config import configure_logging | ||||
| from .structs.landmark import Landmark | ||||
| from .structs.preferences import Preferences, Preference | ||||
| from .structs.linked_landmarks import LinkedLandmarks | ||||
| from .structs.trip import Trip | ||||
| from .landmarks.landmarks_manager import LandmarkManager | ||||
| from .toilets.toilets_route import router as toilets_router | ||||
| from .optimization.optimization_routes import router as optimization_router | ||||
| from .landmarks.landmarks_routes import router as landmarks_router | ||||
| from .toilets.toilets_router import router as toilets_router | ||||
| from .optimization.optimization_router import router as optimization_router | ||||
| from .landmarks.landmarks_router import router as landmarks_router, get_landmarks_nearby | ||||
| from .optimization.optimizer import Optimizer | ||||
| from .optimization.refiner import Refiner | ||||
| from .overpass.overpass import fill_cache | ||||
| from .cache import client as cache_client | ||||
|  | ||||
|  | ||||
| @@ -40,118 +36,21 @@ app = FastAPI(lifespan=lifespan) | ||||
|  | ||||
|  | ||||
|  | ||||
| app.include_router(toilets_router) | ||||
| app.include_router(optimization_router) | ||||
| # Fetches the global list of landmarks given preferences and start/end coordinates. Two routes | ||||
| # Call with "/get/landmarks/" for main entry point of the trip generation pipeline. | ||||
| # Call with "/get-nearby/landmarks/" for the NEARBY feature. | ||||
| app.include_router(landmarks_router) | ||||
|  | ||||
|  | ||||
| @app.post("/trip/new") | ||||
| def new_trip( | ||||
|     preferences: Preferences, | ||||
|     start: tuple[float, float], | ||||
|     end: tuple[float, float] | None = None, | ||||
|     background_tasks: BackgroundTasks = None | ||||
| ) -> Trip: | ||||
|     """ | ||||
|     Main function to call the optimizer. | ||||
| # Optimizes the trip given preferences. Second step in the main trip generation pipeline | ||||
| # Call with "/optimize/trip" | ||||
| app.include_router(optimization_router) | ||||
|  | ||||
|     Args: | ||||
|         preferences : the preferences specified by the user as the post body | ||||
|         start       : the coordinates of the starting point | ||||
|         end         : the coordinates of the finishing point | ||||
|     Returns:  | ||||
|         (uuid) : The uuid of the first landmark in the optimized route | ||||
|     """ | ||||
|     if preferences is None: | ||||
|         raise HTTPException(status_code=406, detail="Preferences not provided or incomplete.") | ||||
|     if (preferences.shopping.score == 0 and | ||||
|         preferences.sightseeing.score == 0 and | ||||
|         preferences.nature.score == 0) : | ||||
|         raise HTTPException(status_code=406, detail="All preferences are 0.") | ||||
|     if start is None: | ||||
|         raise HTTPException(status_code=406, detail="Start coordinates not provided") | ||||
|     if not (-90 <= start[0] <= 90 or -180 <= start[1] <= 180): | ||||
|         raise HTTPException(status_code=422, detail="Start coordinates not in range") | ||||
|     if end is None: | ||||
|         end = start | ||||
|         logger.info("No end coordinates provided. Using start=end.") | ||||
|  | ||||
|     logger.info(f"Requested new trip generation. Details:\n\tCoordinates: {start}\n\tTime: {preferences.max_time_minute}\n\tSightseeing: {preferences.sightseeing.score}\n\tNature: {preferences.nature.score}\n\tShopping: {preferences.shopping.score}") | ||||
| # Fetches toilets near given coordinates. | ||||
| # Call with "/get/toilets" for fetching toilets around coordinates | ||||
| app.include_router(toilets_router) | ||||
|  | ||||
|     start_landmark = Landmark(name='start', | ||||
|                               type='start', | ||||
|                               location=(start[0], start[1]), | ||||
|                               osm_type='start', | ||||
|                               osm_id=0, | ||||
|                               attractiveness=0, | ||||
|                               duration=0, | ||||
|                               must_do=True, | ||||
|                               n_tags = 0) | ||||
|  | ||||
|     end_landmark = Landmark(name='finish', | ||||
|                             type='finish', | ||||
|                             location=(end[0], end[1]), | ||||
|                             osm_type='end', | ||||
|                             osm_id=0, | ||||
|                             attractiveness=0, | ||||
|                             duration=0, | ||||
|                             must_do=True, | ||||
|                             n_tags=0) | ||||
|  | ||||
|     start_time = time.time() | ||||
|  | ||||
|     # Generate the landmarks from the start location | ||||
|     landmarks, landmarks_short = manager.generate_landmarks_list( | ||||
|         center_coordinates = start, | ||||
|         preferences = preferences | ||||
|     ) | ||||
|  | ||||
|     if len(landmarks) == 0 : | ||||
|         raise HTTPException(status_code=500, detail="No landmarks were found.") | ||||
|  | ||||
|     # insert start and finish to the landmarks list | ||||
|     landmarks_short.insert(0, start_landmark) | ||||
|     landmarks_short.append(end_landmark) | ||||
|  | ||||
|     t_generate_landmarks = time.time() - start_time | ||||
|     logger.info(f'Fetched {len(landmarks)} landmarks in  \t: {round(t_generate_landmarks,3)} seconds') | ||||
|     start_time = time.time() | ||||
|  | ||||
|     # First stage optimization | ||||
|     try: | ||||
|         base_tour = optimizer.solve_optimization(preferences.max_time_minute, landmarks_short) | ||||
|     except Exception as exc: | ||||
|         logger.error(f"Trip generation failed: {str(exc)}") | ||||
|         raise HTTPException(status_code=500, detail=f"Optimization failed: {str(exc)}") from exc | ||||
|  | ||||
|     t_first_stage = time.time() - start_time | ||||
|     start_time = time.time() | ||||
|  | ||||
|     # Second stage optimization | ||||
|     # TODO : only if necessary (not enough landmarks for ex.) | ||||
|     try : | ||||
|         refined_tour = refiner.refine_optimization(landmarks, base_tour, | ||||
|                                                preferences.max_time_minute, | ||||
|                                                preferences.detour_tolerance_minute) | ||||
|     except Exception as exc : | ||||
|         logger.warning(f"Refiner failed. Proceeding with base trip {str(exc)}") | ||||
|         refined_tour = base_tour | ||||
|  | ||||
|     t_second_stage = time.time() - start_time | ||||
|  | ||||
|     logger.debug(f'First stage optimization\t: {round(t_first_stage,3)} seconds') | ||||
|     logger.debug(f'Second stage optimization\t: {round(t_second_stage,3)} seconds') | ||||
|     logger.info(f'Total computation time\t: {round(t_first_stage + t_second_stage,3)} seconds') | ||||
|     linked_tour = LinkedLandmarks(refined_tour) | ||||
|  | ||||
|     # upon creation of the trip, persistence of both the trip and its landmarks is ensured. | ||||
|     trip = Trip.from_linked_landmarks(linked_tour, cache_client) | ||||
|     logger.info(f'Generated a trip of {trip.total_time} minutes with {len(refined_tour)} landmarks in {round(t_generate_landmarks + t_first_stage + t_second_stage,3)} seconds.') | ||||
|     logger.info('Detailed trip :\n\t' + '\n\t'.join(f'{landmark}' for landmark in refined_tour)) | ||||
|  | ||||
|     background_tasks.add_task(fill_cache) | ||||
|  | ||||
|     return trip | ||||
|  | ||||
|  | ||||
| #### For already existing trips/landmarks | ||||
|   | ||||
| @@ -1,19 +1,20 @@ | ||||
| """Main app for backend api""" | ||||
| """API entry point for the trip optimization.""" | ||||
| 
 | ||||
| import logging | ||||
| import time | ||||
| import yaml | ||||
| from fastapi import HTTPException, APIRouter, BackgroundTasks | ||||
| 
 | ||||
| from .optimizer import Optimizer | ||||
| from .refiner import Refiner | ||||
| from ..structs.landmark import Landmark | ||||
| from ..structs.preferences import Preferences | ||||
| from ..structs.linked_landmarks import LinkedLandmarks | ||||
| from ..utils.take_most_important import take_most_important | ||||
| from ..structs.trip import Trip | ||||
| from ..optimization.optimizer import Optimizer | ||||
| from ..optimization.refiner import Refiner | ||||
| from ..overpass.overpass import fill_cache | ||||
| from ..cache import client as cache_client | ||||
| from ..constants import LANDMARK_PARAMETERS_PATH | ||||
| from ..constants import OPTIMIZER_PARAMETERS_PATH | ||||
| 
 | ||||
| 
 | ||||
| # Setup the Logger, Optimizer and Refiner | ||||
| @@ -22,18 +23,18 @@ optimizer = Optimizer() | ||||
| refiner = Refiner(optimizer=optimizer) | ||||
| 
 | ||||
| 
 | ||||
| # Start the router | ||||
| # Initialize the API router | ||||
| router = APIRouter() | ||||
| 
 | ||||
| 
 | ||||
| @router.post("/optimize/trip") | ||||
| def optimize_trip( | ||||
|     preferences: Preferences, | ||||
|     landmarks: list[Landmark], | ||||
|     start: tuple[float, float], | ||||
|     end: tuple[float, float] | None = None, | ||||
|     background_tasks: BackgroundTasks = None | ||||
| ) -> Trip: | ||||
|         preferences: Preferences, | ||||
|         landmarks: list[Landmark], | ||||
|         start: tuple[float, float], | ||||
|         end: tuple[float, float] | None = None, | ||||
|         background_tasks: BackgroundTasks = None | ||||
|     ) -> Trip: | ||||
|     """ | ||||
|     Main function to call the optimizer. | ||||
| 
 | ||||
| @@ -66,28 +67,32 @@ def optimize_trip( | ||||
| 
 | ||||
|     logger.info(f"Requested new trip generation. Details:\n\tCoordinates: {start}\n\tTime: {preferences.max_time_minute}\n\tSightseeing: {preferences.sightseeing.score}\n\tNature: {preferences.nature.score}\n\tShopping: {preferences.shopping.score}") | ||||
| 
 | ||||
|     start_landmark = Landmark(name='start', | ||||
|                               type='start', | ||||
|                               location=(start[0], start[1]), | ||||
|                               osm_type='start', | ||||
|                               osm_id=0, | ||||
|                               attractiveness=0, | ||||
|                               duration=0, | ||||
|                               must_do=True, | ||||
|                               n_tags = 0) | ||||
|     start_landmark = Landmark( | ||||
|         name='start', | ||||
|         type='start', | ||||
|         location=(start[0], start[1]), | ||||
|         osm_type='start', | ||||
|         osm_id=0, | ||||
|         attractiveness=0, | ||||
|         duration=0, | ||||
|         must_do=True, | ||||
|         n_tags = 0 | ||||
|     ) | ||||
| 
 | ||||
|     end_landmark = Landmark(name='finish', | ||||
|                             type='finish', | ||||
|                             location=(end[0], end[1]), | ||||
|                             osm_type='end', | ||||
|                             osm_id=0, | ||||
|                             attractiveness=0, | ||||
|                             duration=0, | ||||
|                             must_do=True, | ||||
|                             n_tags=0) | ||||
|     end_landmark = Landmark( | ||||
|         name='finish', | ||||
|         type='finish', | ||||
|         location=(end[0], end[1]), | ||||
|         osm_type='end', | ||||
|         osm_id=0, | ||||
|         attractiveness=0, | ||||
|         duration=0, | ||||
|         must_do=True, | ||||
|         n_tags=0 | ||||
|     ) | ||||
|      | ||||
|     # From the parameters load the length at which to truncate the landmarks list. | ||||
|     with LANDMARK_PARAMETERS_PATH.open('r') as f: | ||||
|     with OPTIMIZER_PARAMETERS_PATH.open('r') as f: | ||||
|             parameters = yaml.safe_load(f) | ||||
|             n_important = parameters['N_important'] | ||||
| 
 | ||||
| @@ -7,5 +7,4 @@ tag_exponent: 1.15 | ||||
| image_bonus: 1.1 | ||||
| viewpoint_bonus: 10 | ||||
| wikipedia_bonus: 1.25 | ||||
| N_important: 60 | ||||
| pay_bonus: -1 | ||||
|   | ||||
| @@ -6,4 +6,5 @@ max_landmarks_refiner: 20 | ||||
| overshoot: 0.0016 | ||||
| time_limit: 1 | ||||
| gap_rel: 0.025 | ||||
| max_iter: 80 | ||||
| max_iter: 80 | ||||
| N_important: 60 | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| """Defines the Preferences used as input for trip generation.""" | ||||
|  | ||||
| from typing import Optional, Literal | ||||
| from pydantic import BaseModel | ||||
| from pydantic import BaseModel, field_validator | ||||
|  | ||||
|  | ||||
| class Preference(BaseModel) : | ||||
| @@ -15,6 +15,13 @@ class Preference(BaseModel) : | ||||
|     type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish'] | ||||
|     score: int          # score could be from 1 to 5 | ||||
|  | ||||
|     @field_validator("type") | ||||
|     @classmethod | ||||
|     def validate_type(cls, v): | ||||
|         if v not in {'sightseeing', 'nature', 'shopping', 'start', 'finish'}: | ||||
|             raise ValueError(f"Invalid type: {v}") | ||||
|         return v | ||||
|  | ||||
|  | ||||
| # Input for optimization | ||||
| class Preferences(BaseModel) : | ||||
|   | ||||
| @@ -19,15 +19,35 @@ def invalid_client(): | ||||
|         ([48.8566, 2.3522], {}, 422), | ||||
|  | ||||
|         # Invalid cases: incomplete preferences. | ||||
|         ([48.084588, 7.280405], {"sightseeing": {"type": "sightseeing", "score": 5}, # no shopping | ||||
|         ([48.084588, 7.280405], {"sightseeing": {"type": "sightseeing", "score": 5},    # no shopping pref | ||||
|                                  "nature": {"type": "nature", "score": 5}, | ||||
|                                  }, 422), | ||||
|         ([48.084588, 7.280405], {"sightseeing": {"type": "sightseeing", "score": 5}, # no nature | ||||
|         ([48.084588, 7.280405], {"sightseeing": {"type": "sightseeing", "score": 5},    # no nature pref | ||||
|                                  "shopping": {"type": "shopping", "score": 5}, | ||||
|                                  }, 422), | ||||
|         ([48.084588, 7.280405], {"nature": {"type": "nature", "score": 5},      # no sightseeing | ||||
|         ([48.084588, 7.280405], {"nature": {"type": "nature", "score": 5},              # no sightseeing pref | ||||
|                                  "shopping": {"type": "shopping", "score": 5}, | ||||
|                                  }, 422), | ||||
|         ([48.084588, 7.280405], {"sightseeing": {"type": "nature", "score": 1},         # mixed up preferences types. TODO: i suggest reducing the complexity by remove the Preference object. | ||||
|                                  "nature": {"type": "shopping", "score": 1}, | ||||
|                                  "shopping": {"type": "shopping", "score": 1}, | ||||
|                                  }, 422), | ||||
|         ([48.084588, 7.280405], {"doesnotexist": {"type": "sightseeing", "score": 2},   # non-existing preferences types | ||||
|                                  "nature": {"type": "nature", "score": 2}, | ||||
|                                  "shopping": {"type": "shopping", "score": 2}, | ||||
|                                  }, 422), | ||||
|         ([48.084588, 7.280405], {"sightseeing": {"type": "sightseeing", "score": 3},    # non-existing preferences types | ||||
|                                  "nature": {"type": "doesntexisteither", "score": 3}, | ||||
|                                  "shopping": {"type": "shopping", "score": 3}, | ||||
|                                  }, 422), | ||||
|         ([48.084588, 7.280405], {"sightseeing": {"type": "sightseeing", "score": -1},   # negative preference value | ||||
|                                  "nature": {"type": "doesntexisteither", "score": 4}, | ||||
|                                  "shopping": {"type": "shopping", "score": 4}, | ||||
|                                  }, 422), | ||||
|         ([48.084588, 7.280405], {"sightseeing": {"type": "sightseeing", "score": 10},   # too high preference value | ||||
|                                  "nature": {"type": "doesntexisteither", "score": 4}, | ||||
|                                  "shopping": {"type": "shopping", "score": 4}, | ||||
|                                  }, 422), | ||||
|  | ||||
|         # Invalid cases: unexisting coords | ||||
|         ([91, 181], {"sightseeing": {"type": "sightseeing", "score": 5}, | ||||
| @@ -53,8 +73,8 @@ def test_input(invalid_client, start, preferences, status_code):   # pylint: dis | ||||
|     Test new trip creation with different sets of preferences and locations. | ||||
|     """ | ||||
|     response = invalid_client.post( | ||||
|         "/trip/new", | ||||
|         json={ | ||||
|         "/get/landmarks", | ||||
|         json ={ | ||||
|             "preferences": preferences, | ||||
|             "start": start | ||||
|         } | ||||
|   | ||||
| @@ -1,22 +1,32 @@ | ||||
| """Collection of tests to ensure correct implementation and track progress of the get_landmarks_nearby feature. """ | ||||
|  | ||||
| from fastapi.testclient import TestClient | ||||
| import pytest | ||||
|  | ||||
| from ..main import app | ||||
|  | ||||
|  | ||||
| @pytest.fixture(scope="module") | ||||
| def client(): | ||||
|     """Client used to call the app.""" | ||||
|     return TestClient(app) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "location,status_code", | ||||
|     [ | ||||
|         ([45.7576485, 4.8330241], 2000),     # Lyon, Bellecour. | ||||
|         ([41.4020572, 2.1818985], 2000),       # Barcelona | ||||
|         ([45.7576485, 4.8330241], 200),    # Lyon, France | ||||
|         ([41.4020572, 2.1818985], 200),    # Barcelona, Spain | ||||
|         ([59.3293, 18.0686], 200),         # Stockholm, Sweden | ||||
|         ([43.6532, -79.3832], 200),        # Toronto, Canada | ||||
|         ([38.7223, -9.1393], 200),         # Lisbon, Portugal | ||||
|         ([6.5244, 3.3792], 200),           # Lagos, Nigeria | ||||
|         ([17.3850, 78.4867], 200),         # Hyderabad, India | ||||
|         ([30.0444, 31.2357], 200),         # Cairo, Egypt | ||||
|         ([50.8503, 4.3517], 200),          # Brussels, Belgium | ||||
|         ([35.2271, -80.8431], 200),        # Charlotte, USA | ||||
|         ([10.4806, -66.9036], 200),        # Caracas, Venezuela | ||||
|         ([9.51074, -13.71118], 200),       # Conakry, Guinea | ||||
|     ] | ||||
| ) | ||||
| def test_nearby(client, location, status_code):    # pylint: disable=redefined-outer-name | ||||
| @@ -27,9 +37,7 @@ def test_nearby(client, location, status_code):    # pylint: disable=redefined-o | ||||
|         client: | ||||
|         request: | ||||
|     """ | ||||
|     response = client.post(f"/landmarks/get-nearby/{location[0]}/{location[1]}") | ||||
|     print(response) | ||||
|     print(response.json()) | ||||
|     response = client.post(f"/get-nearby/landmarks/{location[0]}/{location[1]}") | ||||
|     suggestions = response.json() | ||||
|  | ||||
|     # checks : | ||||
|   | ||||
| @@ -18,7 +18,7 @@ def client(): | ||||
|     [ | ||||
|         ({}, None, 422),                # Invalid case: no location at all. | ||||
|         ([443], None, 422),             # Invalid cases: invalid location. | ||||
|         ([443, 433], None, 422),             # Invalid cases: invalid location. | ||||
|         ([443, 433], None, 422),        # Invalid cases: invalid location. | ||||
|     ] | ||||
| ) | ||||
| def test_invalid_input(client, location, radius, status_code):    # pylint: disable=redefined-outer-name | ||||
| @@ -30,12 +30,13 @@ def test_invalid_input(client, location, radius, status_code):    # pylint: disa | ||||
|         request: | ||||
|     """ | ||||
|     response = client.post( | ||||
|         "/toilets/new", | ||||
|         "/get/toilets", | ||||
|         params={ | ||||
|             "location": location, | ||||
|             "radius": radius | ||||
|         } | ||||
|     ) | ||||
|     print(response.json()) | ||||
|  | ||||
|     # checks : | ||||
|     assert response.status_code == status_code | ||||
| @@ -58,11 +59,12 @@ def test_no_toilets(client, location, status_code):    # pylint: disable=redefin | ||||
|         request: | ||||
|     """ | ||||
|     response = client.post( | ||||
|         "/toilets/new", | ||||
|         "/get/toilets", | ||||
|         params={ | ||||
|             "location": location | ||||
|         } | ||||
|     ) | ||||
|     print(response.json()) | ||||
|     toilets_list = [Toilets.model_validate(toilet) for toilet in response.json()] | ||||
|  | ||||
|     # checks : | ||||
| @@ -87,12 +89,14 @@ def test_toilets(client, location, status_code):    # pylint: disable=redefined- | ||||
|         request: | ||||
|     """ | ||||
|     response = client.post( | ||||
|         "/toilets/new", | ||||
|         "/get/toilets", | ||||
|         params={ | ||||
|             "location": location, | ||||
|             "radius" : 600 | ||||
|         } | ||||
|     ) | ||||
|  | ||||
|     print(response.json()) | ||||
|     toilets_list = [Toilets.model_validate(toilet) for toilet in response.json()] | ||||
|  | ||||
|     # checks : | ||||
|   | ||||
| @@ -23,9 +23,9 @@ def client(): | ||||
|         # Realistic | ||||
|         (5, 0, 0, 20, [48.0845881, 7.2804050], None),                   # Turckheim | ||||
|         (5, 5, 5, 120, [45.7576485, 4.8330241], None),                  # Lyon, Bellecour | ||||
|         (5, 5, 5, 240, [50.9423526, 6.9577780], None),                  # Cologne, centre | ||||
|         (5, 5, 5, 180, [48.5846589226, 7.74078715721], None),           # Strasbourg, centre | ||||
|         (5, 5, 5, 180, [47.377884227, 8.5395114066], None),             # Zurich, centre | ||||
|         (5, 2, 5, 240, [50.9423526, 6.9577780], None),                  # Cologne, centre | ||||
|         (3, 5, 0, 180, [48.5846589226, 7.74078715721], None),           # Strasbourg, centre | ||||
|         (2, 4, 5, 180, [47.377884227, 8.5395114066], None),             # Zurich, centre | ||||
|         (5, 0, 5, 200, [48.85468881798671, 2.3423925755998374], None),  # Paris, centre | ||||
|         (5, 5, 5, 600, [40.72592726802, -73.9920434795], None),         # New York, Lower Manhattan | ||||
|     ] | ||||
|   | ||||
| @@ -1,16 +1,20 @@ | ||||
| """Defines the endpoint for fetching toilet locations.""" | ||||
| """API entry point for fetching toilet locations.""" | ||||
| 
 | ||||
| from fastapi import HTTPException, APIRouter, Query | ||||
| 
 | ||||
| from ..structs.toilets import Toilets | ||||
| from .toilets_manager import ToiletsManager | ||||
| from ..structs.toilets import Toilets | ||||
| 
 | ||||
| 
 | ||||
| # Define the API router | ||||
| # Initialize the API router | ||||
| router = APIRouter() | ||||
| 
 | ||||
| 
 | ||||
| @router.post("/toilets/new") | ||||
| def get_toilets(location: tuple[float, float] = Query(...), radius: int = 500) -> list[Toilets] : | ||||
| @router.post("/get/toilets") | ||||
| def get_toilets( | ||||
|         location: tuple[float, float] = Query(...), | ||||
|         radius: int = 500 | ||||
|     ) -> list[Toilets] : | ||||
|     """ | ||||
|     Endpoint to find toilets within a specified radius from a given location. | ||||
| 
 | ||||
| @@ -1,24 +0,0 @@ | ||||
| """Helper function to return only the major landmarks from a large list.""" | ||||
| from ..structs.landmark import Landmark | ||||
|  | ||||
| def take_most_important( | ||||
|     landmarks: list[Landmark], | ||||
|     n_important: int | ||||
| ) -> list[Landmark]: | ||||
|     """ | ||||
|     Given a list of landmarks, return the most important landmarks based on their attractiveness. | ||||
|      | ||||
|     Args: | ||||
|         landmarks (list[Landmark]): List of landmarks that needs to be truncated | ||||
|         n_important (int): Number of most important landmarks to return | ||||
|      | ||||
|     Returns: | ||||
|         list[Landmark]: List of the n_important most important landmarks | ||||
|     """ | ||||
|     if n_important == 0 : | ||||
|         raise ValueError('Number of landmarks to keep cannot be zero.') | ||||
|  | ||||
|     # Sort landmarks by attractiveness (descending) | ||||
|     sorted_landmarks = sorted(landmarks, key=lambda x: x.attractiveness, reverse=True) | ||||
|  | ||||
|     return sorted_landmarks[:n_important] | ||||
							
								
								
									
										1091
									
								
								report.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1091
									
								
								report.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user
	
Be brave! Delete it
Removed in commit 510aabcb0a