Phase 3: Create govmap package structure (step 1/3)
Created modular package structure: - validators.py: Input validation functions (3 functions, ~100 lines) - utils.py: Helper utilities (3 functions, ~140 lines) - filters.py: Deal filtering logic (1 main function, ~140 lines) - statistics.py: Statistical calculations (2 functions, ~130 lines) All functions extracted from monolithic govmap.py as pure functions. Next: Extract market analysis and create client.py with API methods. Part of Phase 3 refactoring - no functionality changes yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
Utility functions for Govmap client.
|
||||
|
||||
This module provides shared helper functions with no external dependencies
|
||||
(except standard library).
|
||||
"""
|
||||
|
||||
from typing import 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 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)
|
||||
if len(search_address) > 5 and len(deal_address) > 5:
|
||||
if search_address in deal_address or deal_address in search_address:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
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
|
||||
import re
|
||||
|
||||
numbers = re.findall(r"\d+", floor_str)
|
||||
if numbers:
|
||||
try:
|
||||
return int(numbers[0])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user