Files
Nitzan Pomerantz b87cb268d5 Improve: MCP efficiency & fix radius filtering
- Remove bloat fields (shape, objectid, etc), add sequential IDs
- Add lang param (he/en) for Hebrew/English text values
- Reduce JSON whitespace (indent=None)
- Fix distance_meters: extract centroid from WKT shape geometry
- Add search_coordinates to all address-based tool responses
- Fix radius filtering: properly filter deals beyond radius_meters
- Change default radius from 30m to 50m
- Fix get_deal_statistics: return NO deals (stats only)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 09:34:42 +02:00

175 lines
5.0 KiB
Python

"""
Utility functions for Govmap client.
This module provides shared helper functions with no external dependencies
(except standard library).
"""
import re
from typing import Optional, Tuple
def calculate_distance(point1: Tuple[float, float], point2: Tuple[float, float]) -> float:
"""
Calculate Euclidean distance between two points in ITM coordinates.
ITM (Israeli Transverse Mercator) uses meters as units, so Euclidean
distance provides accurate results for distances within Israel.
Args:
point1: (longitude, latitude) in ITM
point2: (longitude, latitude) in ITM
Returns:
Distance in meters
"""
dx = point2[0] - point1[0]
dy = point2[1] - point1[1]
return (dx * dx + dy * dy) ** 0.5
def extract_shape_centroid(shape_wkt: Optional[str]) -> Optional[Tuple[float, float]]:
"""
Extract centroid coordinates from WKT geometry (MULTIPOLYGON/POLYGON).
Parses WKT string and calculates centroid as average of all coordinate points.
Args:
shape_wkt: WKT geometry string (e.g., "MULTIPOLYGON(...)")
Returns:
(longitude, latitude) tuple in ITM coordinates, or None if parsing fails
"""
if not shape_wkt or not isinstance(shape_wkt, str):
return None
try:
# Extract all coordinate pairs using regex
# Matches: "number.number number.number" or "number number"
coord_pattern = r"([\d.]+)\s+([\d.]+)"
matches = re.findall(coord_pattern, shape_wkt)
if not matches:
return None
# Calculate average (centroid)
lons = [float(m[0]) for m in matches]
lats = [float(m[1]) for m in matches]
centroid_lon = sum(lons) / len(lons)
centroid_lat = sum(lats) / len(lats)
return (centroid_lon, centroid_lat)
except (ValueError, ZeroDivisionError):
return None
def is_same_building(search_address: str, deal_address: str) -> bool:
"""
Check if a deal is from the same building as the search address.
Args:
search_address: The normalized search address (lowercase, stripped)
deal_address: The normalized deal address (lowercase, stripped)
Returns:
True if likely the same building, False otherwise
"""
if not search_address or not deal_address:
return False
# Exact match
if search_address == deal_address:
return True
# Extract key components for comparison
def extract_address_parts(addr: str) -> tuple:
"""Extract street name and number from address"""
# Remove common prefixes/suffixes and normalize
addr_clean = (
addr.replace("רח'", "").replace("רחוב", "").replace("שד'", "").replace("שדרות", "")
)
addr_clean = addr_clean.replace(" ", " ").strip()
# Try to extract number and street name
parts = addr_clean.split()
if len(parts) >= 2:
# Look for number (could be at start or end)
for i, part in enumerate(parts):
if part.isdigit() or any(c.isdigit() for c in part):
number = part
street_parts = parts[:i] + parts[i + 1 :]
street_name = " ".join(street_parts).strip()
return (street_name, number)
return (addr_clean, "")
search_street, search_number = extract_address_parts(search_address)
deal_street, deal_number = extract_address_parts(deal_address)
# Same street and same number = same building
if (
search_street
and deal_street
and search_number
and deal_number
and search_street == deal_street
and search_number == deal_number
):
return True
# Check if one address is contained in the other (for different formats of same address)
return (
len(search_address) > 5
and len(deal_address) > 5
and (search_address in deal_address or deal_address in search_address)
)
def extract_floor_number(floor_str: str) -> int | None:
"""
Extract numeric floor number from Hebrew floor description.
Args:
floor_str: Floor description string (e.g., "שלישית", "קומה 3", "3")
Returns:
Floor number or None if cannot be extracted
"""
if not floor_str:
return None
# Hebrew ordinal floor names to numbers
hebrew_floors = {
"קרקע": 0,
"מרתף": -1,
"ראשונה": 1,
"שניה": 2,
"שלישית": 3,
"רביעית": 4,
"חמישית": 5,
"שישית": 6,
"שביעית": 7,
"שמינית": 8,
"תשיעית": 9,
"עשירית": 10,
}
floor_lower = floor_str.lower().strip()
# Check for direct match with Hebrew names
for heb, num in hebrew_floors.items():
if heb in floor_lower:
return num
# Try to extract number from string
numbers = re.findall(r"\d+", floor_str)
if numbers:
try:
return int(numbers[0])
except ValueError:
pass
return None