diff --git a/nadlan_mcp/fastmcp_server.py b/nadlan_mcp/fastmcp_server.py index 867f4d2..d7d21eb 100644 --- a/nadlan_mcp/fastmcp_server.py +++ b/nadlan_mcp/fastmcp_server.py @@ -96,33 +96,37 @@ def autocomplete_address(search_text: str) -> str: @mcp.tool() def get_deals_by_radius(latitude: float, longitude: float, radius_meters: int = 500) -> str: - """Get real estate deals within a radius of coordinates. - + """Get polygon metadata within a radius of coordinates. + + **NOTE**: This returns polygon/area metadata, NOT individual deal transactions! + Use find_recent_deals_for_address() to get actual deals. + Args: latitude: Latitude coordinate - longitude: Longitude coordinate + longitude: Longitude coordinate radius_meters: Search radius in meters (default: 500) - + Returns: - JSON string containing recent real estate deals in the area + JSON string containing polygon metadata (areas with deals nearby) """ try: # Note: GovmapClient expects (longitude, latitude) tuple - deals = client.get_deals_by_radius((longitude, latitude), radius_meters) - - if not deals: - return f"No deals found within {radius_meters}m of coordinates ({latitude}, {longitude})" - + # Returns polygon metadata dicts, not Deal objects + polygons = client.get_deals_by_radius((longitude, latitude), radius_meters) + + if not polygons: + return f"No polygons found within {radius_meters}m of coordinates ({latitude}, {longitude})" + return json.dumps({ - "total_deals": len(deals), + "total_polygons": len(polygons), "search_radius_meters": radius_meters, "center_coordinates": {"latitude": latitude, "longitude": longitude}, - "deals": strip_bloat_fields(deals) + "polygons": polygons # Return dicts directly, no stripping needed }, ensure_ascii=False, indent=2) - + except Exception as e: logger.error(f"Error in get_deals_by_radius: {e}") - return f"Error fetching deals by radius: {str(e)}" + return f"Error fetching polygons by radius: {str(e)}" @mcp.tool() def get_street_deals(polygon_id: str, limit: int = 100, deal_type: int = 2) -> str: diff --git a/nadlan_mcp/govmap/client.py b/nadlan_mcp/govmap/client.py index 8f2ca8c..9dc8a89 100644 --- a/nadlan_mcp/govmap/client.py +++ b/nadlan_mcp/govmap/client.py @@ -256,16 +256,22 @@ class GovmapClient: def get_deals_by_radius( self, point: Tuple[float, float], radius: int = 50 - ) -> List[Deal]: + ) -> List[Dict[str, Any]]: """ - Find real estate deals within a specified radius of a point. + Find polygon metadata within a specified radius of a point. + + **NOTE**: This endpoint returns polygon metadata, NOT actual deal transactions! + The response contains polygon_id, dealscount, settlementNameHeb, etc. + To get actual deals, extract polygon_ids and call get_street_deals() or get_neighborhood_deals(). + + Use find_recent_deals_for_address() for the complete workflow. Args: point: A tuple of (longitude, latitude) radius: The search radius in meters (default: 50) Returns: - List of Deal models found within the radius + List of polygon metadata dicts (not Deal objects) Raises: requests.RequestException: If the API request fails after retries @@ -293,17 +299,16 @@ class GovmapClient: f"Expected list response, got {type(data).__name__}" ) - # Parse each deal dict into Deal model - deals = [] - for deal_dict in data: - try: - deal = Deal.model_validate(deal_dict) - deals.append(deal) - except Exception as e: - logger.warning(f"Failed to parse deal: {e}. Skipping deal.") - continue + # NOTE: This endpoint returns polygon metadata, not actual deals! + # The response contains: dealscount, polygon_id, settlementNameHeb, streetNameHeb, houseNum, objectid + # We return these as-is (raw dicts) since they're used only for extracting polygon_ids + # in find_recent_deals_for_address() + logger.info(f"Found {len(data)} polygon metadata records") - return deals + # For backward compatibility, return empty list of Deals since these aren't actual deals + # The raw data is available in the response but we don't try to validate as Deal objects + # TODO: Consider creating a PolygonMetadata model for type safety + return data # Return raw dicts temporarily except (requests.RequestException, requests.Timeout) as e: if attempt < self.config.max_retries: @@ -586,14 +591,14 @@ class GovmapClient: search_address_normalized = address.lower().strip() logger.info(f"Found coordinates: {point}") - # Step 2: Get deals by radius to find polygon IDs - nearby_deals = self.get_deals_by_radius(point, radius=radius) + # Step 2: Get polygon metadata by radius to find polygon IDs + nearby_polygons = self.get_deals_by_radius(point, radius=radius) - # Extract unique polygon IDs + # Extract unique polygon IDs from metadata dicts polygon_ids = set() - for deal in nearby_deals: - # Try to get polygon_id from the deal model - polygon_id = getattr(deal, 'polygon_id', None) or deal.source_polygon_id + for metadata in nearby_polygons: + # Extract polygon_id from dict (these are polygon metadata, not deals) + polygon_id = metadata.get('polygon_id') if polygon_id: polygon_ids.add(str(polygon_id)) diff --git a/tests/test_fastmcp_tools.py b/tests/test_fastmcp_tools.py index b7c5685..7207d3f 100644 --- a/tests/test_fastmcp_tools.py +++ b/tests/test_fastmcp_tools.py @@ -130,59 +130,57 @@ class TestGetDealsByRadius: @patch('nadlan_mcp.fastmcp_server.client') def test_successful_get_deals(self, mock_client): - """Test successful deal retrieval.""" - # Mock with Deal models - mock_deals = [ - Deal( - objectid=123, - deal_amount=2000000, - deal_date="2023-01-01", - asset_area=80.0, - street_name="דיזנגוף" - ) + """Test successful polygon metadata retrieval.""" + # Mock with polygon metadata dicts (not Deal objects) + mock_polygons = [ + { + "objectid": 123, + "dealscount": "30", + "settlementNameHeb": "תל אביב-יפו", + "streetNameHeb": "דיזנגוף", + "houseNum": 50, + "polygon_id": "123-456" + } ] - mock_client.get_deals_by_radius.return_value = mock_deals + mock_client.get_deals_by_radius.return_value = mock_polygons result = fastmcp_server.get_deals_by_radius(650000.0, 180000.0, 500) parsed = json.loads(result) - assert len(parsed["deals"]) == 1 - assert parsed["deals"][0]["deal_amount"] == 2000000 # Use snake_case field name - assert parsed["total_deals"] == 1 + assert len(parsed["polygons"]) == 1 + assert parsed["polygons"][0]["dealscount"] == "30" + assert parsed["total_polygons"] == 1 mock_client.get_deals_by_radius.assert_called_once() @patch('nadlan_mcp.fastmcp_server.client') def test_get_deals_no_results(self, mock_client): - """Test deal retrieval with no results.""" + """Test polygon metadata retrieval with no results.""" mock_client.get_deals_by_radius.return_value = [] result = fastmcp_server.get_deals_by_radius(650000.0, 180000.0, 500) - assert "No deals found" in result or json.loads(result)["total_deals"] == 0 + assert "No polygons found" in result @patch('nadlan_mcp.fastmcp_server.client') def test_get_deals_strips_bloat_fields(self, mock_client): - """Test that bloat fields are stripped from response.""" - # Mock with Deal models, not dicts - mock_deals = [ - Deal( - objectid=123, - deal_amount=2000000, - deal_date="2023-01-01", - shape="MULTIPOLYGON(...huge data...)", - sourceorder=1, - source_polygon_id="abc123" - ) + """Test that polygon metadata is returned as-is.""" + # Mock with polygon metadata dicts + mock_polygons = [ + { + "objectid": 123, + "dealscount": "10", + "polygon_id": "abc123", + "settlementNameHeb": "Tel Aviv" + } ] - mock_client.get_deals_by_radius.return_value = mock_deals + mock_client.get_deals_by_radius.return_value = mock_polygons result = fastmcp_server.get_deals_by_radius(650000.0, 180000.0, 500) parsed = json.loads(result) - # Verify bloat fields are removed - deal = parsed["deals"][0] - assert "shape" not in deal - assert "sourceorder" not in deal - # source_polygon_id is kept when added by our processing + # Polygon metadata is returned as-is (no stripping needed) + polygon = parsed["polygons"][0] + assert polygon["polygon_id"] == "abc123" + assert polygon["dealscount"] == "10" @patch('nadlan_mcp.fastmcp_server.client') def test_get_deals_error_handling(self, mock_client): diff --git a/tests/test_govmap_client.py b/tests/test_govmap_client.py index 5c6433b..1df5b4b 100644 --- a/tests/test_govmap_client.py +++ b/tests/test_govmap_client.py @@ -128,14 +128,16 @@ class TestGovmapClient: @patch('requests.Session') def test_get_deals_by_radius_success(self, mock_session_class): - """Test successful deals by radius query.""" + """Test successful polygon metadata retrieval by radius.""" mock_response = Mock() + # API returns polygon metadata (not actual deals) mock_response.json.return_value = [ { "objectid": 12345, - "dealAmount": 1500000.0, - "dealDate": "2024-01-15", + "dealscount": "30", "settlementNameHeb": "תל אביב-יפו", + "streetNameHeb": "דיזנגוף", + "houseNum": 50, "polygon_id": "123-456" } ] @@ -148,11 +150,11 @@ class TestGovmapClient: client = GovmapClient() result = client.get_deals_by_radius((3870000.123, 3770000.456), radius=50) - # Now returns List[Deal] + # Now returns List[Dict] - polygon metadata, not Deal objects assert len(result) == 1 - assert isinstance(result[0], Deal) - assert result[0].objectid == 12345 - assert result[0].settlement_name_heb == "תל אביב-יפו" + assert isinstance(result[0], dict) + assert result[0]["objectid"] == 12345 + assert result[0]["polygon_id"] == "123-456" mock_session.get.assert_called_once() @patch('requests.Session') @@ -245,9 +247,9 @@ class TestGovmapClient: ] ) - # Mock radius response - now returns List[Deal] + # Mock radius response - now returns List[Dict] (polygon metadata) mock_radius.return_value = [ - Deal(objectid=1, deal_amount=1500000, deal_date="2025-01-01", polygon_id="123-456") + {"objectid": 1, "dealscount": "10", "polygon_id": "123-456", "settlementNameHeb": "Tel Aviv"} ] # Mock street deals response - now returns List[Deal]