add place link metadata for suggestions
This commit is contained in:
@@ -228,6 +228,47 @@ def _parse_address(tags):
|
|||||||
return ", ".join([p for p in parts if p])
|
return ", ".join([p for p in parts if p])
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_official_website(tags):
|
||||||
|
if not isinstance(tags, dict):
|
||||||
|
return None
|
||||||
|
|
||||||
|
website_fields = [
|
||||||
|
"website",
|
||||||
|
"contact:website",
|
||||||
|
"official_website",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
for field in website_fields:
|
||||||
|
value = tags.get(field)
|
||||||
|
if isinstance(value, str):
|
||||||
|
normalized = value.strip()
|
||||||
|
if normalized:
|
||||||
|
return normalized
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_osm_map_url(item):
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
return None
|
||||||
|
|
||||||
|
osm_type = item.get("type")
|
||||||
|
osm_id = item.get("id")
|
||||||
|
if osm_type in {"node", "way", "relation"} and osm_id is not None:
|
||||||
|
return f"https://www.openstreetmap.org/{osm_type}/{osm_id}"
|
||||||
|
|
||||||
|
latitude = item.get("lat")
|
||||||
|
longitude = item.get("lon")
|
||||||
|
if latitude is None or longitude is None:
|
||||||
|
center = item.get("center") or {}
|
||||||
|
latitude = center.get("lat")
|
||||||
|
longitude = center.get("lon")
|
||||||
|
|
||||||
|
if latitude is None or longitude is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return f"https://www.openstreetmap.org/?mlat={latitude}&mlon={longitude}"
|
||||||
|
|
||||||
|
|
||||||
@agent_tool(
|
@agent_tool(
|
||||||
name="search_places",
|
name="search_places",
|
||||||
description=(
|
description=(
|
||||||
@@ -309,6 +350,10 @@ def search_places(
|
|||||||
if latitude is None or longitude is None:
|
if latitude is None or longitude is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
official_website = _extract_official_website(tags)
|
||||||
|
map_url = _build_osm_map_url(item)
|
||||||
|
preferred_link = official_website or map_url
|
||||||
|
|
||||||
results.append(
|
results.append(
|
||||||
{
|
{
|
||||||
"name": name,
|
"name": name,
|
||||||
@@ -316,6 +361,9 @@ def search_places(
|
|||||||
"latitude": latitude,
|
"latitude": latitude,
|
||||||
"longitude": longitude,
|
"longitude": longitude,
|
||||||
"category": category,
|
"category": category,
|
||||||
|
"link": preferred_link,
|
||||||
|
"official_website": official_website,
|
||||||
|
"map_url": map_url,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1521,6 +1521,91 @@ class SearchPlaces429NonRetryableTests(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchPlacesLinkMetadataTests(TestCase):
|
||||||
|
@patch("chat.agent_tools.requests.post")
|
||||||
|
@patch("chat.agent_tools.requests.get")
|
||||||
|
def test_search_places_prefers_official_website_over_map_fallback(
|
||||||
|
self,
|
||||||
|
mock_get,
|
||||||
|
mock_post,
|
||||||
|
):
|
||||||
|
mock_get_response = MagicMock()
|
||||||
|
mock_get_response.json.return_value = [{"lat": "48.85837", "lon": "2.294481"}]
|
||||||
|
mock_get_response.raise_for_status.return_value = None
|
||||||
|
mock_get.return_value = mock_get_response
|
||||||
|
|
||||||
|
mock_post_response = MagicMock()
|
||||||
|
mock_post_response.json.return_value = {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 12345,
|
||||||
|
"lat": 48.85837,
|
||||||
|
"lon": 2.294481,
|
||||||
|
"tags": {
|
||||||
|
"name": "Eiffel Tower",
|
||||||
|
"website": "https://www.toureiffel.paris/en",
|
||||||
|
"contact:website": "https://fallback.example.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
mock_post_response.raise_for_status.return_value = None
|
||||||
|
mock_post.return_value = mock_post_response
|
||||||
|
|
||||||
|
result = search_places(user=None, location="Paris, France", category="tourism")
|
||||||
|
|
||||||
|
self.assertIn("results", result)
|
||||||
|
self.assertEqual(len(result["results"]), 1)
|
||||||
|
place = result["results"][0]
|
||||||
|
self.assertEqual(place.get("link"), "https://www.toureiffel.paris/en")
|
||||||
|
self.assertEqual(
|
||||||
|
place.get("official_website"),
|
||||||
|
"https://www.toureiffel.paris/en",
|
||||||
|
)
|
||||||
|
self.assertTrue(place.get("map_url", "").startswith("https://"))
|
||||||
|
|
||||||
|
@patch("chat.agent_tools.requests.post")
|
||||||
|
@patch("chat.agent_tools.requests.get")
|
||||||
|
def test_search_places_uses_map_fallback_when_no_official_website(
|
||||||
|
self,
|
||||||
|
mock_get,
|
||||||
|
mock_post,
|
||||||
|
):
|
||||||
|
mock_get_response = MagicMock()
|
||||||
|
mock_get_response.json.return_value = [{"lat": "41.9028", "lon": "12.4964"}]
|
||||||
|
mock_get_response.raise_for_status.return_value = None
|
||||||
|
mock_get.return_value = mock_get_response
|
||||||
|
|
||||||
|
mock_post_response = MagicMock()
|
||||||
|
mock_post_response.json.return_value = {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "way",
|
||||||
|
"id": 98765,
|
||||||
|
"center": {"lat": 41.8902, "lon": 12.4922},
|
||||||
|
"tags": {
|
||||||
|
"name": "Colosseum",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
mock_post_response.raise_for_status.return_value = None
|
||||||
|
mock_post.return_value = mock_post_response
|
||||||
|
|
||||||
|
result = search_places(user=None, location="Rome, Italy", category="tourism")
|
||||||
|
|
||||||
|
self.assertIn("results", result)
|
||||||
|
self.assertEqual(len(result["results"]), 1)
|
||||||
|
place = result["results"][0]
|
||||||
|
self.assertIsNone(place.get("official_website"))
|
||||||
|
self.assertEqual(place.get("link"), place.get("map_url"))
|
||||||
|
self.assertEqual(
|
||||||
|
place.get("map_url"),
|
||||||
|
"https://www.openstreetmap.org/way/98765",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GetWeatherCoordFallbackTests(APITransactionTestCase):
|
class GetWeatherCoordFallbackTests(APITransactionTestCase):
|
||||||
"""get_weather lat/lng required param should be retried with collection location coords."""
|
"""get_weather lat/lng required param should be retried with collection location coords."""
|
||||||
|
|
||||||
@@ -2015,3 +2100,116 @@ class DaySuggestionsCoordinateEnrichmentTests(TestCase):
|
|||||||
self.assertEqual(len(enriched), 1)
|
self.assertEqual(len(enriched), 1)
|
||||||
self.assertNotIn("latitude", enriched[0])
|
self.assertNotIn("latitude", enriched[0])
|
||||||
self.assertNotIn("longitude", enriched[0])
|
self.assertNotIn("longitude", enriched[0])
|
||||||
|
|
||||||
|
def test_preserves_existing_coordinates_and_link_when_present(self):
|
||||||
|
suggestions = [
|
||||||
|
{
|
||||||
|
"name": "Known Place",
|
||||||
|
"location": "Somewhere",
|
||||||
|
"latitude": 10.5,
|
||||||
|
"longitude": 20.5,
|
||||||
|
"link": "https://existing.example.com",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
place_candidates = [
|
||||||
|
{
|
||||||
|
"name": "Known Place",
|
||||||
|
"address": "Somewhere",
|
||||||
|
"latitude": 1.0,
|
||||||
|
"longitude": 2.0,
|
||||||
|
"link": "https://candidate.example.com",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
enriched = self.view._enrich_suggestions_with_coordinates(
|
||||||
|
suggestions,
|
||||||
|
place_candidates,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(enriched[0]["latitude"], 10.5)
|
||||||
|
self.assertEqual(enriched[0]["longitude"], 20.5)
|
||||||
|
self.assertEqual(enriched[0]["link"], "https://existing.example.com")
|
||||||
|
|
||||||
|
def test_fills_missing_coordinates_and_link_from_place_match(self):
|
||||||
|
suggestions = [
|
||||||
|
{
|
||||||
|
"name": "Roscioli",
|
||||||
|
"location": "Via dei Giubbonari, Rome",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
place_candidates = [
|
||||||
|
{
|
||||||
|
"name": "Roscioli",
|
||||||
|
"address": "Via dei Giubbonari, Rome",
|
||||||
|
"latitude": 41.8933,
|
||||||
|
"longitude": 12.4722,
|
||||||
|
"link": "https://www.roscioli.com",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
enriched = self.view._enrich_suggestions_with_coordinates(
|
||||||
|
suggestions,
|
||||||
|
place_candidates,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(enriched), 1)
|
||||||
|
self.assertEqual(enriched[0]["latitude"], 41.8933)
|
||||||
|
self.assertEqual(enriched[0]["longitude"], 12.4722)
|
||||||
|
self.assertEqual(enriched[0]["link"], "https://www.roscioli.com")
|
||||||
|
|
||||||
|
def test_preserves_existing_link_while_filling_missing_coordinates(self):
|
||||||
|
suggestions = [
|
||||||
|
{
|
||||||
|
"name": "Borough Market",
|
||||||
|
"location": "Southwark",
|
||||||
|
"link": "https://existing.example.com/borough",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
place_candidates = [
|
||||||
|
{
|
||||||
|
"name": "Borough Market",
|
||||||
|
"address": "8 Southwark St, London",
|
||||||
|
"latitude": 51.5055,
|
||||||
|
"longitude": -0.0904,
|
||||||
|
"link": "https://candidate.example.com/borough",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
enriched = self.view._enrich_suggestions_with_coordinates(
|
||||||
|
suggestions,
|
||||||
|
place_candidates,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(enriched), 1)
|
||||||
|
self.assertEqual(enriched[0]["latitude"], 51.5055)
|
||||||
|
self.assertEqual(enriched[0]["longitude"], -0.0904)
|
||||||
|
self.assertEqual(enriched[0]["link"], "https://existing.example.com/borough")
|
||||||
|
|
||||||
|
def test_preserves_existing_coordinates_while_filling_missing_link(self):
|
||||||
|
suggestions = [
|
||||||
|
{
|
||||||
|
"name": "City Museum",
|
||||||
|
"location": "Old Town",
|
||||||
|
"latitude": 40.7128,
|
||||||
|
"longitude": -74.006,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
place_candidates = [
|
||||||
|
{
|
||||||
|
"name": "City Museum",
|
||||||
|
"address": "Old Town",
|
||||||
|
"latitude": 40.7128,
|
||||||
|
"longitude": -74.006,
|
||||||
|
"link": "https://citymuseum.example.com",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
enriched = self.view._enrich_suggestions_with_coordinates(
|
||||||
|
suggestions,
|
||||||
|
place_candidates,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(enriched), 1)
|
||||||
|
self.assertEqual(enriched[0]["latitude"], 40.7128)
|
||||||
|
self.assertEqual(enriched[0]["longitude"], -74.006)
|
||||||
|
self.assertEqual(enriched[0]["link"], "https://citymuseum.example.com")
|
||||||
|
|||||||
@@ -370,28 +370,34 @@ class DaySuggestionsView(APIView):
|
|||||||
if not isinstance(suggestion, dict):
|
if not isinstance(suggestion, dict):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (
|
|
||||||
suggestion.get("latitude") is not None
|
|
||||||
and suggestion.get("longitude") is not None
|
|
||||||
):
|
|
||||||
enriched.append(suggestion)
|
|
||||||
continue
|
|
||||||
|
|
||||||
matched_place = self._best_place_match(suggestion, place_candidates)
|
|
||||||
if not matched_place:
|
|
||||||
enriched.append(suggestion)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if (
|
|
||||||
matched_place.get("latitude") is None
|
|
||||||
or matched_place.get("longitude") is None
|
|
||||||
):
|
|
||||||
enriched.append(suggestion)
|
|
||||||
continue
|
|
||||||
|
|
||||||
merged = dict(suggestion)
|
merged = dict(suggestion)
|
||||||
|
has_coordinates = (
|
||||||
|
merged.get("latitude") is not None
|
||||||
|
and merged.get("longitude") is not None
|
||||||
|
)
|
||||||
|
has_link = bool(merged.get("link"))
|
||||||
|
|
||||||
|
if has_coordinates and has_link:
|
||||||
|
enriched.append(merged)
|
||||||
|
continue
|
||||||
|
|
||||||
|
matched_place = self._best_place_match(merged, place_candidates)
|
||||||
|
if not matched_place:
|
||||||
|
enriched.append(merged)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not has_coordinates and (
|
||||||
|
matched_place.get("latitude") is not None
|
||||||
|
and matched_place.get("longitude") is not None
|
||||||
|
):
|
||||||
merged["latitude"] = matched_place.get("latitude")
|
merged["latitude"] = matched_place.get("latitude")
|
||||||
merged["longitude"] = matched_place.get("longitude")
|
merged["longitude"] = matched_place.get("longitude")
|
||||||
|
|
||||||
|
if not has_link:
|
||||||
|
matched_link = matched_place.get("link")
|
||||||
|
if matched_link:
|
||||||
|
merged["link"] = matched_link
|
||||||
|
|
||||||
merged["location"] = merged.get("location") or matched_place.get("address")
|
merged["location"] = merged.get("location") or matched_place.get("address")
|
||||||
enriched.append(merged)
|
enriched.append(merged)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user