hybrid cache now
Some checks failed
Run testing on the backend code / Build (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m37s
Run linting on the backend code / Build (pull_request) Successful in 28s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 24s

This commit is contained in:
Helldragon67 2025-01-29 09:35:26 +01:00
parent 83c1533e78
commit 21706ea7e6
5 changed files with 120 additions and 47 deletions

View File

@ -328,7 +328,7 @@ div.media {
</head>
<body>
<h1 id="title">Backend Testing Report</h1>
<p>Report generated on 28-Jan-2025 at 16:30:56 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a>
<p>Report generated on 29-Jan-2025 at 09:35:03 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a>
v4.1.1</p>
<div id="environment-header">
<h2>Environment</h2>
@ -382,7 +382,7 @@ div.media {
<h2>Summary</h2>
<div class="additional-summary prefix">
</div>
<p class="run-count">1 test took 00:00:10.</p>
<p class="run-count">1 test took 97 ms.</p>
<p class="filter">(Un)check the boxes to filter the results.</p>
<div class="summary__reload">
<div class="summary__reload__button hidden" onclick="location.reload()">
@ -432,7 +432,7 @@ div.media {
</table>
</body>
<footer>
<div id="data-container" data-jsonblob="{&#34;environment&#34;: {&#34;Python&#34;: &#34;3.12.3&#34;, &#34;Platform&#34;: &#34;Linux-6.8.0-51-generic-x86_64-with-glibc2.39&#34;, &#34;Packages&#34;: {&#34;pytest&#34;: &#34;8.3.4&#34;, &#34;pluggy&#34;: &#34;1.5.0&#34;}, &#34;Plugins&#34;: {&#34;html&#34;: &#34;4.1.1&#34;, &#34;anyio&#34;: &#34;4.8.0&#34;, &#34;metadata&#34;: &#34;3.1.1&#34;}}, &#34;tests&#34;: {&#34;src/tests/test_main.py::test_turckheim&#34;: [{&#34;extras&#34;: [], &#34;result&#34;: &#34;Passed&#34;, &#34;testId&#34;: &#34;src/tests/test_main.py::test_turckheim&#34;, &#34;resultsTableRow&#34;: [&#34;&lt;td class=\&#34;col-result\&#34;&gt;Passed&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-testId\&#34;&gt;src/tests/test_main.py::test_turckheim&lt;/td&gt;&#34;, &#34;&lt;td&gt;start (0 | 0) - 6 - H\u00f4tel des Deux-Clefs (217 | 5) - 1 - H\u00f4tel de ville (238 | 5) - 6 - finish (0 | 0) - 0&lt;/td&gt;&#34;, &#34;&lt;td&gt;23 min&lt;/td&gt;&#34;, &#34;&lt;td&gt;20 min&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-duration\&#34;&gt;00:00:10&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-links\&#34;&gt;&lt;/td&gt;&#34;], &#34;log&#34;: &#34;------------------------------ Captured log call -------------------------------\nDEBUG asyncio:selector_events.py:64 Using selector: EpollSelector\nINFO src.main:main.py:67 No end coordinates provided. Using start=end.\nDEBUG src.utils.landmarks_manager:landmarks_manager.py:76 Starting to fetch landmarks...\nDEBUG src.utils.landmarks_manager:landmarks_manager.py:88 Fetching sightseeing landmarks...\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nDEBUG src.utils.landmarks_manager:landmarks_manager.py:204 Fetched 20 landmarks of type sightseeing in (48.079458619727674, 7.272726663391359, 48.08971738027232, 7.288083336608641)\nINFO src.utils.landmarks_manager:landmarks_manager.py:91 Found 20 sightseeing landmarks\nINFO src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO src.utils.cluster_manager:cluster_manager.py:145 Found 0 sightseeing clusters.\nDEBUG src.utils.landmarks_manager:landmarks_manager.py:100 Fetching nature landmarks...\nINFO src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:59 Cache miss for 1 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 1 quadrants.\nDEBUG src.utils.landmarks_manager:landmarks_manager.py:204 Fetched 10 landmarks of type nature in (48.079458619727674, 7.272726663391359, 48.08971738027232, 7.288083336608641)\nINFO src.utils.landmarks_manager:landmarks_manager.py:103 Found 10 nature landmarks\nINFO src.main:main.py:104 Fetched 22 landmarks in \t: 5.367 seconds\nDEBUG src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting.\nINFO src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 455\nDEBUG src.optimization.refiner:refiner.py:345 Using 5 minor landmarks around the predicted path\nDEBUG src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting.\nINFO src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 455\nDEBUG src.main:main.py:130 First stage optimization\t: 0.126 seconds\nDEBUG src.main:main.py:131 Second stage optimization\t: 0.033 seconds\nINFO src.main:main.py:132 Total computation time\t: 0.159 seconds\nINFO src.main:main.py:137 Generated a trip of 23 minutes with 4 landmarks in 5.526 seconds.\nDEBUG src.overpass.overpass:overpass.py:112 Cache set for b2fd047a07f9563c8c0925aad9d61052\nDEBUG src.overpass.overpass:overpass.py:112 Cache set for 7ee38297ba20e3bc47b984dce2785e22\nDEBUG src.overpass.overpass:overpass.py:112 Cache set for 7f98015c7bc3a9100e10bc1c1ddfa572\nDEBUG src.overpass.overpass:overpass.py:112 Cache set for 43424dbebba69d06a66a34728c3bb93e\nDEBUG src.overpass.overpass:overpass.py:112 Cache set for b7d4ee874adac132bc9e80d7172fab8e\nINFO httpx:_client.py:1025 HTTP Request: POST http://testserver/trip/new &amp;quot;HTTP/1.1 200 OK&amp;quot;\n\n&#34;}]}, &#34;renderCollapsed&#34;: [&#34;passed&#34;], &#34;initialSort&#34;: &#34;result&#34;, &#34;title&#34;: &#34;Backend Testing Report&#34;}"></div>
<div id="data-container" data-jsonblob="{&#34;environment&#34;: {&#34;Python&#34;: &#34;3.12.3&#34;, &#34;Platform&#34;: &#34;Linux-6.8.0-51-generic-x86_64-with-glibc2.39&#34;, &#34;Packages&#34;: {&#34;pytest&#34;: &#34;8.3.4&#34;, &#34;pluggy&#34;: &#34;1.5.0&#34;}, &#34;Plugins&#34;: {&#34;html&#34;: &#34;4.1.1&#34;, &#34;anyio&#34;: &#34;4.8.0&#34;, &#34;metadata&#34;: &#34;3.1.1&#34;}}, &#34;tests&#34;: {&#34;src/tests/test_main.py::test_turckheim&#34;: [{&#34;extras&#34;: [], &#34;result&#34;: &#34;Passed&#34;, &#34;testId&#34;: &#34;src/tests/test_main.py::test_turckheim&#34;, &#34;resultsTableRow&#34;: [&#34;&lt;td class=\&#34;col-result\&#34;&gt;Passed&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-testId\&#34;&gt;src/tests/test_main.py::test_turckheim&lt;/td&gt;&#34;, &#34;&lt;td&gt;start (0 | 0) - 3 - Mairie du 2e arrondissement (78 | 5) - 1 - Basilique Saint-Martin d&#39;Ainay (406 | 5) - 3 - Chapelle Paul Couturier (109 | 5) - 1 - finish (0 | 0) - 0&lt;/td&gt;&#34;, &#34;&lt;td&gt;23 min&lt;/td&gt;&#34;, &#34;&lt;td&gt;20 min&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-duration\&#34;&gt;97 ms&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-links\&#34;&gt;&lt;/td&gt;&#34;], &#34;log&#34;: &#34;------------------------------ Captured log call -------------------------------\nDEBUG asyncio:selector_events.py:64 Using selector: EpollSelector\nINFO src.main:main.py:67 No end coordinates provided. Using start=end.\nDEBUG src.utils.landmarks_manager:landmarks_manager.py:76 Starting to fetch landmarks...\nDEBUG src.utils.landmarks_manager:landmarks_manager.py:88 Fetching sightseeing landmarks...\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nDEBUG src.utils.landmarks_manager:landmarks_manager.py:204 Fetched 16 landmarks of type sightseeing in (45.74643460077641, 4.819803369760263, 45.756693361321055, 4.834505559895031)\nINFO src.utils.landmarks_manager:landmarks_manager.py:91 Found 16 sightseeing landmarks\nINFO src.overpass.overpass:overpass.py:55 Cache hit for 2/2 quadrants.\nINFO src.utils.cluster_manager:cluster_manager.py:145 Found 0 sightseeing clusters.\nINFO src.main:main.py:104 Fetched 12 landmarks in \t: 0.011 seconds\nDEBUG src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting.\nINFO src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 593\nDEBUG src.optimization.refiner:refiner.py:345 Using 2 minor landmarks around the predicted path\nDEBUG src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting.\nINFO src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 593\nDEBUG src.main:main.py:130 First stage optimization\t: 0.045 seconds\nDEBUG src.main:main.py:131 Second stage optimization\t: 0.025 seconds\nINFO src.main:main.py:132 Total computation time\t: 0.071 seconds\nINFO src.main:main.py:137 Generated a trip of 23 minutes with 5 landmarks in 0.082 seconds.\nINFO httpx:_client.py:1025 HTTP Request: POST http://testserver/trip/new &amp;quot;HTTP/1.1 200 OK&amp;quot;\n\n&#34;}]}, &#34;renderCollapsed&#34;: [&#34;passed&#34;], &#34;initialSort&#34;: &#34;result&#34;, &#34;title&#34;: &#34;Backend Testing Report&#34;}"></div>
<script>
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
const { getCollapsedCategory, setCollapsedIds } = require('./storage.js')

View File

@ -2,7 +2,7 @@
import os
from pathlib import Path
from typing import List, Literal
from typing import List, Literal, Tuple
LOCATION_PREFIX = Path('src')
@ -16,6 +16,7 @@ cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache')
OSM_CACHE_DIR = Path(cache_dir_string)
OSM_TYPES = List[Literal['way', 'node', 'relation']]
BBOX = Tuple[float, float, float, float]
MEMCACHED_HOST_PATH = os.getenv('MEMCACHED_HOST_PATH', None)
if MEMCACHED_HOST_PATH == "none":

View File

@ -54,7 +54,8 @@ class JSONCache(CachingStrategyBase):
# Open and parse the cached JSON data
with open(filename, 'r', encoding='utf-8') as file:
data = json.load(file)
return data # Return the parsed JSON data
# Return the data as a list of dicts.
return data
except json.JSONDecodeError:
return None # Return None if parsing fails
return None

View File

@ -4,12 +4,14 @@ import urllib
import math
import logging
import json
from typing import List, Tuple
from .caching_strategy import get_cache_key, CachingStrategy
from ..constants import OSM_CACHE_DIR, OSM_TYPES
from ..constants import OSM_CACHE_DIR, OSM_TYPES, BBOX
RESOLUTION = 0.05
CELL = Tuple[int, int]
class Overpass :
@ -29,8 +31,8 @@ class Overpass :
self.caching_strategy = CachingStrategy.use(caching_strategy, cache_dir=cache_dir)
def send_query(self, bbox: tuple, osm_types: OSM_TYPES,
selector: str, conditions: list=None, out='center'):
def send_query(self, bbox: BBOX, osm_types: OSM_TYPES,
selector: str, conditions: list=None, out='center') -> List[dict]:
"""
Sends the Overpass QL query to the Overpass API and returns the parsed json response.
@ -42,28 +44,35 @@ class Overpass :
out (str): Output format ('center', 'body', etc.). Defaults to 'center'.
Returns:
dict: Parsed json response from the Overpass API, or cached data if available.
list: Parsed json response from the Overpass API, or cached data if available.
"""
# Determine which grid cells overlap with this bounding box.
overlapping_cells = Overpass._get_overlapping_cells(bbox)
# Retrieve cached data and identify missing cache entries
cached_responses, hollow_cache_keys = self._retrieve_cached_data(overlapping_cells, osm_types, selector, conditions, out)
cached_responses, non_cached_cells = self._retrieve_cached_data(overlapping_cells, osm_types, selector, conditions, out)
# If there is no missing data, return the cached responses
if not hollow_cache_keys :
self.logger.info(f'Cache hit for {len(cached_responses)} quadrants.')
return self._combine_cached_data(cached_responses)
self.logger.info(f'Cache hit for {len(overlapping_cells)-len(non_cached_cells)}/{len(overlapping_cells)} quadrants.')
# TODO If there is SOME missing data : hybrid stuff with partial cache
self.logger.info(f'Cache miss for {len(hollow_cache_keys)} quadrants.')
# If there is no missing data, return the cached responses after filtering.
if not non_cached_cells :
return Overpass._filter_landmarks(cached_responses, bbox)
# Missing data: Make a query to Overpass API
query_str = Overpass.build_query(bbox, osm_types, selector, conditions, out)
return self.fetch_data_from_api(query_str)
# If there is no cached data, fetch all from Overpass.
elif not cached_responses :
query_str = Overpass.build_query(bbox, osm_types, selector, conditions, out)
return self.fetch_data_from_api(query_str)
# Hybrid cache: some data from Overpass, some data from cache.
else :
# Resize the bbox for smaller search area and build new query string.
non_cached_bbox = Overpass._get_non_cached_bbox(non_cached_cells, bbox)
query_str = Overpass.build_query(non_cached_bbox, osm_types, selector, conditions, out)
non_cached_responses = self.fetch_data_from_api(query_str)
return Overpass._filter_landmarks(cached_responses, bbox) + non_cached_responses
def fetch_data_from_api(self, query_str: str) -> list:
def fetch_data_from_api(self, query_str: str) -> List[dict]:
"""
Fetch data from the Overpass API and return the json data.
@ -117,7 +126,7 @@ class Overpass :
@staticmethod
def build_query(bbox: tuple, osm_types: OSM_TYPES,
def build_query(bbox: BBOX, osm_types: OSM_TYPES,
selector: str, conditions: list=None, out='center') -> str:
"""
Constructs a query string for the Overpass API to retrieve OpenStreetMap (OSM) data.
@ -160,9 +169,10 @@ class Overpass :
return query
def _retrieve_cached_data(self, overlapping_cells: list, osm_types: OSM_TYPES, selector: str, conditions: list, out: str):
def _retrieve_cached_data(self, overlapping_cells: CELL, osm_types: OSM_TYPES,
selector: str, conditions: list, out: str) -> Tuple[List[dict], list[CELL]]:
"""
Retrieve cached data and identify missing cache entries.
Retrieve cached data and identify missing cache quadrants.
Args:
overlapping_cells (list): Cells to check for cached data.
@ -174,7 +184,7 @@ class Overpass :
Returns:
tuple: A tuple containing:
- cached_responses (list): List of cached data found.
- hollow_cache_keys (list): List of keys with missing data.
- non_cached_cells (list(tuple)): List of cells with missing data.
"""
cell_key_dict = {}
for cell in overlapping_cells :
@ -184,29 +194,29 @@ class Overpass :
cell_key_dict[cell] = get_cache_key(key_str)
cached_responses = []
hollow_cache_keys = []
non_cached_cells = []
# Retrieve the cached data and mark the missing entries as hollow
for cell, key in cell_key_dict.items():
cached_data = self.caching_strategy.get(key)
if cached_data is not None :
cached_responses.append(cached_data)
cached_responses += cached_data
else:
self.caching_strategy.set_hollow(key, cell, osm_types, selector, conditions, out)
hollow_cache_keys.append(key)
non_cached_cells.append(cell)
return cached_responses, hollow_cache_keys
return cached_responses, non_cached_cells
@staticmethod
def _build_query_from_hollow(json_data: dict):
def _build_query_from_hollow(json_data: dict) -> Tuple[str, str]:
"""
Build query string using information from a hollow cache entry.
"""
# Extract values from the JSON object
key = json_data.get('key')
cell = tuple(json_data.get('cell'))
bbox = Overpass._get_bbox_from_grid_cell(cell[0], cell[1])
bbox = Overpass._get_bbox_from_grid_cell(cell)
osm_types = json_data.get('osm_types')
selector = json_data.get('selector')
conditions = json_data.get('conditions')
@ -218,7 +228,7 @@ class Overpass :
@staticmethod
def _get_overlapping_cells(query_bbox: tuple):
def _get_overlapping_cells(query_bbox: tuple) -> List[CELL]:
"""
Returns a set of all grid cells that overlap with the given bounding box.
"""
@ -237,7 +247,7 @@ class Overpass :
@staticmethod
def _get_grid_cell(lat: float, lon: float):
def _get_grid_cell(lat: float, lon: float) -> CELL:
"""
Returns the grid cell coordinates for a given latitude and longitude.
Each grid cell is 0.05°lat x 0.05°lon resolution in size.
@ -248,7 +258,7 @@ class Overpass :
@staticmethod
def _get_bbox_from_grid_cell(lat_index: int, lon_index: int):
def _get_bbox_from_grid_cell(cell: CELL) -> BBOX:
"""
Returns the bounding box for a given grid cell index.
Each grid cell is resolution x resolution in size.
@ -256,26 +266,84 @@ class Overpass :
The bounding box is returned as (min_lat, min_lon, max_lat, max_lon).
"""
# Calculate the southwest (min_lat, min_lon) corner of the bounding box
min_lat = round(lat_index * RESOLUTION, 2)
min_lon = round(lon_index * RESOLUTION, 2)
min_lat = round(cell[0] * RESOLUTION, 2)
min_lon = round(cell[1] * RESOLUTION, 2)
# Calculate the northeast (max_lat, max_lon) corner of the bounding box
max_lat = round((lat_index + 1) * RESOLUTION, 2)
max_lon = round((lon_index + 1) * RESOLUTION, 2)
max_lat = round((cell[0] + 1) * RESOLUTION, 2)
max_lon = round((cell[1] + 1) * RESOLUTION, 2)
return (min_lat, min_lon, max_lat, max_lon)
@staticmethod
def _combine_cached_data(cached_data_list):
def _get_non_cached_bbox(non_cached_cells: List[CELL], original_bbox: BBOX):
"""
Combines data from multiple cached responses into a single result.
Calculate the non-cached bounding box by excluding cached cells.
Args:
non_cached_cells (list): The list of cells that were not found in the cache.
original_bbox (tuple): The original bounding box (min_lat, min_lon, max_lat, max_lon).
Returns:
tuple: The new bounding box that excludes cached cells, or None if all cells are cached.
"""
combined_data = []
for cached_data in cached_data_list:
for element in cached_data:
combined_data.append(element)
return combined_data
if not non_cached_cells:
return None # All cells were cached
# Initialize the non-cached bounding box with extreme values
min_lat, min_lon, max_lat, max_lon = float('inf'), float('inf'), float('-inf'), float('-inf')
# Iterate over non-cached cells to find the new bounding box
for cell in non_cached_cells:
cell_min_lat, cell_min_lon, cell_max_lat, cell_max_lon = Overpass._get_bbox_from_grid_cell(cell)
min_lat = min(min_lat, cell_min_lat)
min_lon = min(min_lon, cell_min_lon)
max_lat = max(max_lat, cell_max_lat)
max_lon = max(max_lon, cell_max_lon)
# If no update to bounding box, return the original
if min_lat == float('inf') or min_lon == float('inf'):
return None
return (max(min_lat, original_bbox[0]),
max(min_lon, original_bbox[1]),
min(max_lat, original_bbox[2]),
min(max_lon, original_bbox[3]))
@staticmethod
def _filter_landmarks(elements: List[dict], bbox: BBOX) -> List[dict]:
"""
Filters elements based on whether their coordinates are inside the given bbox.
Args:
- elements (list of dict): List of elements containing coordinates.
- bbox (tuple): A bounding box defined as (min_lat, min_lon, max_lat, max_lon).
Returns:
- list: A list of elements whose coordinates are inside the bounding box.
"""
filtered_elements = []
min_lat, min_lon, max_lat, max_lon = bbox
for elem in elements:
# Extract coordinates based on the 'type' of element
if elem.get('type') != 'node':
center = elem.get('center', {})
lat = float(center.get('lat', 0))
lon = float(center.get('lon', 0))
else:
lat = float(elem.get('lat', 0))
lon = float(elem.get('lon', 0))
# Check if the coordinates fall within the given bounding box
if min_lat <= lat <= max_lat and min_lon <= lon <= max_lon:
filtered_elements.append(elem)
return filtered_elements
def get_base_info(elem: dict, osm_type: OSM_TYPES, with_name=False) :

View File

@ -27,11 +27,13 @@ def test_turckheim(client, request): # pylint: disable=redefined-outer-name
"/trip/new",
json={
"preferences": {"sightseeing": {"type": "sightseeing", "score": 5},
"nature": {"type": "nature", "score": 5},
"nature": {"type": "nature", "score": 0},
"shopping": {"type": "shopping", "score": 0},
"max_time_minute": duration_minutes,
"detour_tolerance_minute": 0},
"start": [48.084588, 7.280405]
# "start": [48.084588, 7.280405]
# "start": [45.74445023349939, 4.8222687890538865]
"start": [45.75156398104873, 4.827154464827647]
}
)
result = response.json()
@ -56,7 +58,7 @@ def test_turckheim(client, request): # pylint: disable=redefined-outer-name
assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}"
# assert 2!= 3
'''
def test_bellecour(client, request) : # pylint: disable=redefined-outer-name
"""
Test n°2 : Custom test in Lyon centre to ensure proper decision making in crowded area.
@ -342,3 +344,4 @@ def test_shopping(client, request) : # pylint: disable=redefined-outer-name
assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds"
assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}"
assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}"
'''