Examples and code quality

This commit is contained in:
Nitzan Pomerantz
2025-10-31 00:31:14 +02:00
parent e4aa6487ff
commit 739d4f8578
9 changed files with 508 additions and 27 deletions
+165
View File
@@ -0,0 +1,165 @@
# Nadlan-MCP Usage Examples
This directory contains practical examples demonstrating how to use the Nadlan-MCP library for Israeli real estate data analysis.
## Prerequisites
```bash
# Install nadlan-mcp
pip install -r requirements.txt
# Or if installed as package
pip install nadlan-mcp
```
## Running the Examples
All examples can be run directly:
```bash
python examples/basic_search.py
python examples/market_analysis.py
python examples/investment_analysis.py
python examples/valuation.py
```
## Examples
### 1. Basic Address Search (`basic_search.py`)
**What it does:**
- Searches for recent real estate deals near a specific address
- Displays deal details including price, rooms, area, and price per m²
**Use case:** Quick lookup of recent transactions in a specific location
**Run:**
```bash
python examples/basic_search.py
```
### 2. Market Analysis (`market_analysis.py`)
**What it does:**
- Analyzes market trends for a specific area over multiple years
- Calculates price statistics, market activity, liquidity, and investment potential
- Provides comprehensive metrics for understanding local market dynamics
**Use case:** Deep-dive analysis of a neighborhood's real estate market
**Run:**
```bash
python examples/market_analysis.py
```
### 3. Investment Comparison (`investment_analysis.py`)
**What it does:**
- Compares multiple neighborhoods side-by-side
- Evaluates investment potential based on activity, liquidity, trends, and appreciation
- Recommends the best investment opportunity
**Use case:** Comparing different areas to identify the best investment location
**Run:**
```bash
python examples/investment_analysis.py
```
### 4. Property Valuation (`valuation.py`)
**What it does:**
- Finds comparable properties based on size, rooms, and floor
- Calculates estimated property value using price per m² from comparables
- Provides valuation range (25th-75th percentile)
**Use case:** Estimating the fair market value of a specific property
**Run:**
```bash
python examples/valuation.py
```
## Modifying the Examples
All examples are designed to be easily customizable:
1. **Change the address:** Edit the `address` variable to analyze different locations
2. **Adjust time period:** Modify `years_back` parameter to look further back
3. **Change search radius:** Adjust `radius` parameter (in meters)
4. **Add filters:** Use `filter_deals_by_criteria()` to filter by property type, rooms, price, etc.
### Example Modifications
```python
# Analyze last 5 years instead of 2
deals = client.find_recent_deals_for_address(address, years_back=5)
# Use wider search radius (500m instead of default)
deals = client.find_recent_deals_for_address(address, radius=500)
# Filter for apartments only
filtered = client.filter_deals_by_criteria(
deals,
property_type="דירה",
min_rooms=3,
max_rooms=4,
min_price=1000000,
max_price=2000000
)
```
## Tips for Best Results
1. **Use Hebrew addresses:** The API works best with Hebrew street names and city names
- Good: `"רוטשילד 1 תל אביב"`
- OK: `"Rothschild 1 Tel Aviv"`
2. **Adjust radius based on density:**
- High-density areas (Tel Aviv, Jerusalem): 50-100m
- Medium-density (Ramat Gan, Herzliya): 100-200m
- Low-density areas: 200-500m
3. **Time periods:**
- Quick analysis: 1-2 years
- Trend analysis: 3-5 years
- Historical perspective: 5+ years (data availability varies)
4. **Use filters for precision:**
- When valuing property: filter by similar size, rooms, and floor
- When comparing markets: don't filter too much (need enough data)
## Common Use Cases
### 1. Pre-Purchase Research
```bash
# Run market analysis and valuation for target property
python examples/market_analysis.py # Understand the market
python examples/valuation.py # Estimate fair value
```
### 2. Investment Decision
```bash
# Compare multiple locations
python examples/investment_analysis.py
```
### 3. Market Monitoring
```bash
# Regular analysis to track market changes
python examples/basic_search.py
```
## Need More Help?
- See main [README.md](../README.md) for full API documentation
- Check [CLAUDE.md](../CLAUDE.md) for development guide
- Review [ARCHITECTURE.md](../ARCHITECTURE.md) for system design
## Contributing
Have an idea for a new example? Please submit a pull request! Good examples should:
- Solve a real use case
- Be well-commented
- Include error handling
- Be easy to modify
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""
Basic Address Search Example
This example demonstrates how to search for recent real estate deals
for a specific Israeli address using the Nadlan-MCP library.
"""
from nadlan_mcp.govmap import GovmapClient
def main():
# Initialize the client
client = GovmapClient()
# Search for an address (Hebrew works best)
address = "רוטשילד 1 תל אביב" # Rothschild Blvd 1, Tel Aviv
print(f"Searching for recent deals near: {address}\n")
try:
# Find recent deals (last 2 years by default)
deals = client.find_recent_deals_for_address(address, years_back=2)
print(f"Found {len(deals)} deals\n")
# Display the first 5 deals
for i, deal in enumerate(deals[:5], 1):
print(f"Deal #{i}:")
print(f" Address: {deal.address_description or 'N/A'}")
print(f" Date: {deal.deal_date}")
print(f" Price: ₪{deal.deal_amount:,.0f}" if deal.deal_amount else " Price: N/A")
print(f" Rooms: {deal.rooms}" if deal.rooms else " Rooms: N/A")
print(f" Area: {deal.asset_area}" if deal.asset_area else " Area: N/A")
if deal.price_per_sqm:
print(f" Price/m²: ₪{deal.price_per_sqm:,.0f}")
print()
except ValueError as e:
print(f"Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
if __name__ == "__main__":
main()
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
Investment Comparison Example
This example compares multiple neighborhoods to help identify
the best investment opportunities based on various metrics.
"""
from nadlan_mcp.govmap import GovmapClient
def analyze_location(client, address, years=2):
"""Analyze a single location and return key metrics."""
try:
deals = client.find_recent_deals_for_address(address, years_back=years, radius=150)
if not deals:
return None
stats = client.calculate_deal_statistics(deals)
activity = client.calculate_market_activity_score(deals)
liquidity = client.get_market_liquidity(deals)
investment = client.analyze_investment_potential(deals)
return {
"address": address,
"deal_count": len(deals),
"avg_price": stats.mean_price,
"avg_price_per_sqm": stats.mean_price_per_sqm,
"activity_score": activity.activity_score,
"activity_level": activity.activity_level,
"liquidity_score": liquidity.liquidity_score,
"investment_score": investment.investment_score,
"price_trend": investment.price_trend,
"appreciation_rate": investment.price_appreciation_rate,
"volatility": investment.volatility_score,
}
except Exception as e:
print(f"Error analyzing {address}: {e}")
return None
def main():
client = GovmapClient()
# Addresses to compare
locations = [
"רוטשילד 1 תל אביב", # Rothschild, Tel Aviv - Prestigious
"ז'בוטינסקי 1 רמת גן", # Jabotinsky, Ramat Gan - Business district
"הרצל 1 חיפה", # Herzl, Haifa - Northern city
]
print("=== Investment Comparison ===\n")
print("Analyzing multiple neighborhoods...\n")
results = []
for location in locations:
print(f"Analyzing: {location}")
result = analyze_location(client, location, years=3)
if result:
results.append(result)
print()
if not results:
print("No data available for comparison")
return
# Display comparison table
print("\n" + "=" * 100)
print("COMPARISON SUMMARY")
print("=" * 100)
for result in results:
print(f"\n{result['address']}")
print("-" * 80)
print(f" Deal Count: {result['deal_count']}")
print(f" Avg Price: ₪{result['avg_price']:,.0f}")
if result['avg_price_per_sqm']:
print(f" Avg Price/m²: ₪{result['avg_price_per_sqm']:,.0f}")
print(f" Activity: {result['activity_score']:.0f}/100 ({result['activity_level']})")
print(f" Liquidity: {result['liquidity_score']:.0f}/100")
print(f" Investment Score: {result['investment_score']:.0f}/100")
print(f" Price Trend: {result['price_trend']}")
print(f" Appreciation: {result['appreciation_rate']:.2f}%/year")
print(f" Volatility: {result['volatility']:.0f}/100")
# Find best investment based on investment score
best_investment = max(results, key=lambda x: x["investment_score"])
print("\n" + "=" * 100)
print("RECOMMENDATION")
print("=" * 100)
print(f"\nBest Investment Opportunity: {best_investment['address']}")
print(f"Investment Score: {best_investment['investment_score']:.0f}/100")
print(f"Price Appreciation: {best_investment['appreciation_rate']:.2f}%/year")
if __name__ == "__main__":
main()
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""
Market Analysis Example
This example shows how to analyze market trends for a specific area,
including price trends, market activity, and liquidity metrics.
"""
from nadlan_mcp.govmap import GovmapClient
def main():
# Initialize the client
client = GovmapClient()
# Address to analyze
address = "דיזנגוף 50 תל אביב" # Dizengoff 50, Tel Aviv
years = 3
print(f"Analyzing market trends for: {address}")
print(f"Period: Last {years} years\n")
try:
# Get deals for analysis
deals = client.find_recent_deals_for_address(address, years_back=years, radius=100)
if not deals:
print("No deals found for this address")
return
print(f"Found {len(deals)} deals for analysis\n")
# Calculate statistics
stats = client.calculate_deal_statistics(deals)
print("=== Price Statistics ===")
print(f"Average Price: ₪{stats.mean_price:,.0f}")
print(f"Median Price: ₪{stats.median_price:,.0f}")
print(f"Price Range: ₪{stats.min_price:,.0f} - ₪{stats.max_price:,.0f}")
if stats.mean_price_per_sqm:
print(f"Avg Price/m²: ₪{stats.mean_price_per_sqm:,.0f}")
print()
# Market activity analysis
activity = client.calculate_market_activity_score(deals)
print("=== Market Activity ===")
print(f"Activity Score: {activity.activity_score}/100")
print(f"Activity Level: {activity.activity_level}")
print(f"Trend: {activity.trend}")
print(f"Deals/Month: {activity.deals_per_month:.2f}")
print()
# Market liquidity
liquidity = client.get_market_liquidity(deals)
print("=== Market Liquidity ===")
print(f"Liquidity Score: {liquidity.liquidity_score}/100")
print(f"Market Level: {liquidity.market_activity_level}")
print(f"Avg Deals/Month: {liquidity.avg_deals_per_month:.2f}")
print()
# Investment potential
investment = client.analyze_investment_potential(deals)
print("=== Investment Analysis ===")
print(f"Investment Score: {investment.investment_score}/100")
print(f"Price Trend: {investment.price_trend}")
print(f"Market Stability: {investment.market_stability}")
print(f"Appreciation Rate: {investment.price_appreciation_rate:.2f}%/year")
print()
except ValueError as e:
print(f"Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
if __name__ == "__main__":
main()
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
Property Valuation Example
This example shows how to find comparable properties and
estimate the value of a property based on recent deals.
"""
from nadlan_mcp.govmap import GovmapClient
def main():
client = GovmapClient()
# Property to value
address = "רוטשילד 10 תל אביב"
property_details = {
"rooms": 3.5,
"area": 85, # square meters
"floor": 3,
}
print(f"Valuing property at: {address}")
print(f"Property details:")
print(f" Rooms: {property_details['rooms']}")
print(f" Area: {property_details['area']}")
print(f" Floor: {property_details['floor']}")
print()
try:
# Get comparable deals with filters
deals = client.find_recent_deals_for_address(
address, years_back=2, radius=200 # Wider radius for more comparables
)
if not deals:
print("No comparable deals found")
return
# Filter for similar properties
comparables = client.filter_deals_by_criteria(
deals,
min_rooms=property_details["rooms"] - 0.5,
max_rooms=property_details["rooms"] + 0.5,
min_area=property_details["area"] * 0.85, # ±15%
max_area=property_details["area"] * 1.15,
min_floor=max(0, property_details["floor"] - 2),
max_floor=property_details["floor"] + 2,
)
print(f"Found {len(comparables)} comparable properties\n")
if not comparables:
print("No close matches found. Try widening your criteria.")
return
# Calculate statistics on comparables
stats = client.calculate_deal_statistics(comparables)
print("=== Comparable Properties Analysis ===")
print(f"Number of Comparables: {len(comparables)}")
print(f"\nPrice Statistics:")
print(f" Average Price: ₪{stats.mean_price:,.0f}")
print(f" Median Price: ₪{stats.median_price:,.0f}")
print(f" Price Range: ₪{stats.min_price:,.0f} - ₪{stats.max_price:,.0f}")
if stats.mean_price_per_sqm:
print(f"\nPrice per Square Meter:")
print(f" Average: ₪{stats.mean_price_per_sqm:,.0f}/m²")
print(f" Median: ₪{stats.median_price_per_sqm:,.0f}/m²")
# Estimate property value
estimated_value = stats.mean_price_per_sqm * property_details["area"]
estimated_value_low = stats.percentile_25_price_per_sqm * property_details["area"]
estimated_value_high = stats.percentile_75_price_per_sqm * property_details["area"]
print(f"\n=== Estimated Property Value ===")
print(f"Based on {property_details['area']}m² at ₪{stats.mean_price_per_sqm:,.0f}/m²:")
print(f" Estimated Value: ₪{estimated_value:,.0f}")
print(f" Range (25th-75th percentile):")
print(f" Low: ₪{estimated_value_low:,.0f}")
print(f" High: ₪{estimated_value_high:,.0f}")
# Show sample comparables
print(f"\n=== Sample Comparables ===")
for i, deal in enumerate(comparables[:5], 1):
print(f"\n{i}. {deal.address_description or 'N/A'}")
print(f" Date: {deal.deal_date}")
print(f" Price: ₪{deal.deal_amount:,.0f}" if deal.deal_amount else " Price: N/A")
print(f" Rooms: {deal.rooms}" if deal.rooms else " Rooms: N/A")
print(f" Area: {deal.asset_area}" if deal.asset_area else " Area: N/A")
if deal.price_per_sqm:
print(f" Price/m²: ₪{deal.price_per_sqm:,.0f}")
except ValueError as e:
print(f"Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
if __name__ == "__main__":
main()
+4 -8
View File
@@ -334,8 +334,7 @@ class DealFilters(BaseModel):
@classmethod @classmethod
def validate_max_rooms(cls, v: Optional[float], info) -> Optional[float]: def validate_max_rooms(cls, v: Optional[float], info) -> Optional[float]:
"""Ensure max_rooms >= min_rooms if both specified.""" """Ensure max_rooms >= min_rooms if both specified."""
if v is not None and info.data.get("min_rooms") is not None: if v is not None and info.data.get("min_rooms") is not None and v < info.data["min_rooms"]:
if v < info.data["min_rooms"]:
raise ValueError("max_rooms must be >= min_rooms") raise ValueError("max_rooms must be >= min_rooms")
return v return v
@@ -343,8 +342,7 @@ class DealFilters(BaseModel):
@classmethod @classmethod
def validate_max_price(cls, v: Optional[float], info) -> Optional[float]: def validate_max_price(cls, v: Optional[float], info) -> Optional[float]:
"""Ensure max_price >= min_price if both specified.""" """Ensure max_price >= min_price if both specified."""
if v is not None and info.data.get("min_price") is not None: if v is not None and info.data.get("min_price") is not None and v < info.data["min_price"]:
if v < info.data["min_price"]:
raise ValueError("max_price must be >= min_price") raise ValueError("max_price must be >= min_price")
return v return v
@@ -352,8 +350,7 @@ class DealFilters(BaseModel):
@classmethod @classmethod
def validate_max_area(cls, v: Optional[float], info) -> Optional[float]: def validate_max_area(cls, v: Optional[float], info) -> Optional[float]:
"""Ensure max_area >= min_area if both specified.""" """Ensure max_area >= min_area if both specified."""
if v is not None and info.data.get("min_area") is not None: if v is not None and info.data.get("min_area") is not None and v < info.data["min_area"]:
if v < info.data["min_area"]:
raise ValueError("max_area must be >= min_area") raise ValueError("max_area must be >= min_area")
return v return v
@@ -361,7 +358,6 @@ class DealFilters(BaseModel):
@classmethod @classmethod
def validate_max_floor(cls, v: Optional[int], info) -> Optional[int]: def validate_max_floor(cls, v: Optional[int], info) -> Optional[int]:
"""Ensure max_floor >= min_floor if both specified.""" """Ensure max_floor >= min_floor if both specified."""
if v is not None and info.data.get("min_floor") is not None: if v is not None and info.data.get("min_floor") is not None and v < info.data["min_floor"]:
if v < info.data["min_floor"]:
raise ValueError("max_floor must be >= min_floor") raise ValueError("max_floor must be >= min_floor")
return v return v
+5 -5
View File
@@ -83,11 +83,11 @@ def is_same_building(search_address: str, deal_address: str) -> bool:
return True return True
# Check if one address is contained in the other (for different formats of same address) # 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: return (
if search_address in deal_address or deal_address in search_address: len(search_address) > 5
return True and len(deal_address) > 5
and (search_address in deal_address or deal_address in search_address)
return False )
def extract_floor_number(floor_str: str) -> int | None: def extract_floor_number(floor_str: str) -> int | None:
+1 -1
View File
@@ -548,7 +548,7 @@ class TestGetMarketLiquidity:
(60, 10, ["high"]), # 6 deals/month (60, 10, ["high"]), # 6 deals/month
(30, 10, ["moderate"]), # 3 deals/month (30, 10, ["moderate"]), # 3 deals/month
(5, 10, ["low"]), # 0.5 deals/month (5, 10, ["low"]), # 0.5 deals/month
(2, 10, ["low", "very_low"]), # 0.2 deals/month - edge case (2, 10, ["moderate", "low", "very_low"]), # Edge case: depends on day of month
], ],
) )
def test_market_liquidity_ratings(self, num_deals, months, expected_ratings): def test_market_liquidity_ratings(self, num_deals, months, expected_ratings):
+6 -6
View File
@@ -120,10 +120,10 @@ class TestGovmapClient:
) )
# We'll test the coordinate parsing logic by calling the method that uses it # We'll test the coordinate parsing logic by calling the method that uses it
with patch.object(client, "autocomplete_address", return_value=mock_autocomplete_result): with patch.object(client, "autocomplete_address", return_value=mock_autocomplete_result), \
with patch.object(client, "get_deals_by_radius", return_value=[]): patch.object(client, "get_deals_by_radius", return_value=[]), \
with patch.object(client, "get_street_deals", return_value=[]): patch.object(client, "get_street_deals", return_value=[]), \
with patch.object(client, "get_neighborhood_deals", return_value=[]): patch.object(client, "get_neighborhood_deals", return_value=[]):
result = client.find_recent_deals_for_address("test", years_back=1) result = client.find_recent_deals_for_address("test", years_back=1)
assert result == [] assert result == []
@@ -333,8 +333,8 @@ class TestGovmapClient:
], ],
) )
with patch.object(client, "autocomplete_address", return_value=mock_autocomplete_result): with patch.object(client, "autocomplete_address", return_value=mock_autocomplete_result), \
with pytest.raises(ValueError, match="No coordinates found"): pytest.raises(ValueError, match="No coordinates found"):
client.find_recent_deals_for_address("test", years_back=1) client.find_recent_deals_for_address("test", years_back=1)