Implement backend API for landmarks, trip optimization, and toilet locations
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m49s
Run linting on the backend code / Build (pull_request) Successful in 30s
Run testing on the backend code / Build (pull_request) Failing after 45s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 32s
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m49s
Run linting on the backend code / Build (pull_request) Successful in 30s
Run testing on the backend code / Build (pull_request) Failing after 45s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 32s
- Added landmarks_router.py to handle landmark retrieval based on user preferences and location. - Implemented optimization_router.py for trip optimization, including handling preferences and landmarks. - Created toilets_router.py to fetch toilet locations within a specified radius from a given location. - Enhanced error handling and logging across all new endpoints. - Generated a comprehensive report.html for test results and environment details.
This commit is contained in:
parent
54bc9028ad
commit
b0f9d31ee2
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
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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
Loading…
x
Reference in New Issue
Block a user