Merge pull request 'hot fix to reintroduce the /trip/new endpoint' (#74) from fix/missing-endpoint-new-trip into main
Some checks failed
Build and deploy the backend to production / Build and push image (push) Failing after 1m29s
Build and release apps to production track / Get version (push) Has been cancelled
Build and release apps to production track / Build and upload android app (push) Has been cancelled
Build and release apps to production track / Build and upload ios app (push) Has been cancelled

Reviewed-on: #74
This commit is contained in:
2025-12-07 17:19:10 +00:00

View File

@@ -1,12 +1,16 @@
"""Main app for backend api""" """Main app for backend api"""
import logging import logging
import yaml
import time
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException, BackgroundTasks
from .logging_config import configure_logging from .logging_config import configure_logging
from .structs.landmark import Landmark from .structs.landmark import Landmark
from .structs.preferences import Preferences
from .structs.linked_landmarks import LinkedLandmarks from .structs.linked_landmarks import LinkedLandmarks
from .structs.trip import Trip from .structs.trip import Trip
from .overpass.overpass import fill_cache
from .landmarks.landmarks_manager import LandmarkManager from .landmarks.landmarks_manager import LandmarkManager
from .toilets.toilets_router import router as toilets_router from .toilets.toilets_router import router as toilets_router
from .optimization.optimization_router import router as optimization_router from .optimization.optimization_router import router as optimization_router
@@ -14,6 +18,7 @@ from .landmarks.landmarks_router import router as landmarks_router, get_landmark
from .optimization.optimizer import Optimizer from .optimization.optimizer import Optimizer
from .optimization.refiner import Refiner from .optimization.refiner import Refiner
from .cache import client as cache_client from .cache import client as cache_client
from .constants import OPTIMIZER_PARAMETERS_PATH
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -53,6 +58,127 @@ app.include_router(toilets_router)
###### TO REMOVE ONCE THE FRONTEND IS UP TO DATE ######
@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.
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")
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_time = time.time()
# Generate the landmarks from the start location
landmarks = manager.generate_landmarks_list(
center_coordinates = start,
preferences = preferences
)
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()
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
)
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 OPTIMIZER_PARAMETERS_PATH.open('r') as f:
parameters = yaml.safe_load(f)
n_important = parameters['N_important']
# Truncate to the most important landmarks for a shorter list
landmarks_short = landmarks[:n_important]
# insert start and finish to the shorter landmarks list
landmarks_short.insert(0, start_landmark)
landmarks_short.append(end_landmark)
# 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
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'Optimized a trip of {trip.total_time} minutes with {len(refined_tour)} landmarks in {round(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 #### For already existing trips/landmarks
@app.get("/trip/{trip_uuid}") @app.get("/trip/{trip_uuid}")
def get_trip(trip_uuid: str) -> Trip: def get_trip(trip_uuid: str) -> Trip: