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])
|
||||
|
||||
|
||||
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(
|
||||
name="search_places",
|
||||
description=(
|
||||
@@ -309,6 +350,10 @@ def search_places(
|
||||
if latitude is None or longitude is None:
|
||||
continue
|
||||
|
||||
official_website = _extract_official_website(tags)
|
||||
map_url = _build_osm_map_url(item)
|
||||
preferred_link = official_website or map_url
|
||||
|
||||
results.append(
|
||||
{
|
||||
"name": name,
|
||||
@@ -316,6 +361,9 @@ def search_places(
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"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):
|
||||
"""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.assertNotIn("latitude", 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):
|
||||
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["latitude"] = matched_place.get("latitude")
|
||||
merged["longitude"] = matched_place.get("longitude")
|
||||
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["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")
|
||||
enriched.append(merged)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user