Testing update
This commit is contained in:
+152
@@ -0,0 +1,152 @@
|
|||||||
|
# Testing Strategy
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Nadlan-MCP uses a three-tier testing approach:
|
||||||
|
1. **Fast unit tests** - Cached fixtures, run in <1s (default)
|
||||||
|
2. **E2E smoke tests** - Minimal API calls, run in ~5s (verify API works)
|
||||||
|
3. **Comprehensive E2E tests** - Full API coverage, run in ~5min (optional)
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
### Default: Fast Unit Tests Only
|
||||||
|
```bash
|
||||||
|
pytest tests/ --ignore=tests/e2e/
|
||||||
|
# Result: 180 passed, 1 skipped in 0.39s
|
||||||
|
```
|
||||||
|
|
||||||
|
### E2E Smoke Tests (Fast)
|
||||||
|
```bash
|
||||||
|
pytest tests/e2e/test_mcp_tools.py -m integration
|
||||||
|
# Result: 4 passed in 4.77s
|
||||||
|
```
|
||||||
|
|
||||||
|
### Comprehensive E2E Tests (Slow - Optional)
|
||||||
|
```bash
|
||||||
|
pytest tests/e2e/test_mcp_tools_comprehensive.py -m integration
|
||||||
|
# Result: 11 passed in 5m36s
|
||||||
|
```
|
||||||
|
|
||||||
|
### All Tests
|
||||||
|
```bash
|
||||||
|
pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Structure
|
||||||
|
|
||||||
|
### Fast Unit Tests (`tests/`)
|
||||||
|
- **174 tests** covering all core functionality
|
||||||
|
- Use cached API responses from `tests/fixtures/`
|
||||||
|
- Mock `GovmapClient` to avoid real API calls
|
||||||
|
- Run in **0.39 seconds**
|
||||||
|
|
||||||
|
Key test files:
|
||||||
|
- `tests/govmap/test_models.py` - Pydantic model validation (36 tests)
|
||||||
|
- `tests/govmap/test_utils.py` - Helper functions (45 tests)
|
||||||
|
- `tests/govmap/test_validators.py` - Input validation (24 tests)
|
||||||
|
- `tests/test_govmap_client.py` - Client and business logic (41 tests)
|
||||||
|
- `tests/test_fastmcp_tools.py` - MCP tool integration (22 tests)
|
||||||
|
- `tests/test_mcp_tools_fast.py` - Fast MCP tool tests with fixtures (6 tests)
|
||||||
|
|
||||||
|
### E2E Smoke Tests (`tests/e2e/test_mcp_tools.py`)
|
||||||
|
- **4 minimal smoke tests** with real API calls
|
||||||
|
- Verify API connectivity and basic functionality
|
||||||
|
- Use minimal data (small limits, small radius)
|
||||||
|
- Run in **4.77 seconds**
|
||||||
|
|
||||||
|
### Comprehensive E2E Tests (`tests/e2e/test_mcp_tools_comprehensive.py`)
|
||||||
|
- **11 thorough integration tests** with real API calls
|
||||||
|
- Test all 10 MCP tools with full data
|
||||||
|
- Verify complete workflow from MCP tool → API → response
|
||||||
|
- Catch API contract changes
|
||||||
|
- Run in **5min 36sec** (optional, use for releases)
|
||||||
|
|
||||||
|
## Fixtures
|
||||||
|
|
||||||
|
Cached API responses in `tests/fixtures/`:
|
||||||
|
- `autocomplete_response.json` - Address search results
|
||||||
|
- `street_deals.json` - Street-level deal data
|
||||||
|
- `neighborhood_deals.json` - Neighborhood-level deal data
|
||||||
|
- `polygon_metadata.json` - Polygon metadata from radius search
|
||||||
|
|
||||||
|
These fixtures are generated from real API calls and updated as needed.
|
||||||
|
|
||||||
|
## Test Markers
|
||||||
|
|
||||||
|
Configured in `pytest.ini`:
|
||||||
|
- `@pytest.mark.integration` - Slow tests requiring real API calls
|
||||||
|
- `@pytest.mark.unit` - Fast unit tests (default)
|
||||||
|
|
||||||
|
## Performance Comparison
|
||||||
|
|
||||||
|
| Test Suite | Count | Duration | Speed |
|
||||||
|
|------------|-------|----------|-------|
|
||||||
|
| Fast unit tests | 180 | 0.39s | 462 tests/sec |
|
||||||
|
| E2E smoke tests | 4 | 4.77s | 0.84 tests/sec |
|
||||||
|
| Comprehensive E2E | 11 | 5m36s | 0.033 tests/sec |
|
||||||
|
|
||||||
|
**Unit tests are 12x faster than smoke tests, 860x faster than comprehensive E2E!**
|
||||||
|
|
||||||
|
## CI/CD Recommendations
|
||||||
|
|
||||||
|
### PR Checks (~5s)
|
||||||
|
```bash
|
||||||
|
# Run unit tests + smoke tests
|
||||||
|
pytest tests/ --ignore=tests/e2e/test_mcp_tools_comprehensive.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nightly Build (~6min)
|
||||||
|
```bash
|
||||||
|
# Run all tests including comprehensive E2E
|
||||||
|
pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quick Verification (<5s)
|
||||||
|
```bash
|
||||||
|
# Just smoke tests to verify API works
|
||||||
|
pytest tests/e2e/test_mcp_tools.py -m integration
|
||||||
|
```
|
||||||
|
|
||||||
|
## Updating Fixtures
|
||||||
|
|
||||||
|
When API responses change, regenerate fixtures:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source venv/bin/activate
|
||||||
|
python << 'EOF'
|
||||||
|
import json
|
||||||
|
from nadlan_mcp.govmap import GovmapClient
|
||||||
|
|
||||||
|
client = GovmapClient()
|
||||||
|
|
||||||
|
# Update autocomplete fixture
|
||||||
|
autocomplete_response = client.autocomplete_address("חולון סוקולוב")
|
||||||
|
with open("tests/fixtures/autocomplete_response.json", "w", encoding="utf-8") as f:
|
||||||
|
json.dump([r.model_dump() for r in autocomplete_response.results], f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# Update street deals
|
||||||
|
street_deals = client.get_street_deals("52385050", limit=10)
|
||||||
|
with open("tests/fixtures/street_deals.json", "w", encoding="utf-8") as f:
|
||||||
|
json.dump([d.model_dump(mode='json') for d in street_deals], f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# Update neighborhood deals
|
||||||
|
neighborhood_deals = client.get_neighborhood_deals("52385050", limit=10)
|
||||||
|
with open("tests/fixtures/neighborhood_deals.json", "w", encoding="utf-8") as f:
|
||||||
|
json.dump([d.model_dump(mode='json') for d in neighborhood_deals], f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# Update polygon metadata
|
||||||
|
polygons = client.get_deals_by_radius((3870928.84, 3766290.19), radius=500)
|
||||||
|
with open("tests/fixtures/polygon_metadata.json", "w", encoding="utf-8") as f:
|
||||||
|
json.dump(polygons[:10], f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print("✓ Fixtures updated")
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Coverage
|
||||||
|
|
||||||
|
Run with coverage reporting:
|
||||||
|
```bash
|
||||||
|
pytest --cov=nadlan_mcp --cov-report=html
|
||||||
|
open htmlcov/index.html
|
||||||
|
```
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""End-to-end tests for MCP tools."""
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"""
|
||||||
|
Fast E2E smoke tests for MCP tools (<30 seconds).
|
||||||
|
|
||||||
|
These tests make MINIMAL real API calls to verify the service is working.
|
||||||
|
For comprehensive E2E testing, see test_mcp_tools_comprehensive.py
|
||||||
|
|
||||||
|
Target: Complete in <30 seconds
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import pytest
|
||||||
|
from nadlan_mcp.fastmcp_server import (
|
||||||
|
autocomplete_address,
|
||||||
|
find_recent_deals_for_address,
|
||||||
|
get_street_deals,
|
||||||
|
get_deals_by_radius,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
class TestMCPToolsSmokeTests:
|
||||||
|
"""Minimal E2E smoke tests to verify API connectivity."""
|
||||||
|
|
||||||
|
# Known working data
|
||||||
|
TEST_ADDRESS = "סוקולוב 38 חולון"
|
||||||
|
TEST_POLYGON_ID = "52385050"
|
||||||
|
TEST_LAT = 3766290.19
|
||||||
|
TEST_LON = 3870928.84
|
||||||
|
|
||||||
|
def test_autocomplete_works(self):
|
||||||
|
"""Smoke test: Autocomplete returns results."""
|
||||||
|
result = autocomplete_address("חולון")
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert isinstance(data, list)
|
||||||
|
assert len(data) > 0
|
||||||
|
assert "text" in data[0]
|
||||||
|
|
||||||
|
def test_get_street_deals_works(self):
|
||||||
|
"""Smoke test: Can fetch street deals."""
|
||||||
|
# Use small limit for speed
|
||||||
|
result = get_street_deals(self.TEST_POLYGON_ID, limit=5, deal_type=2)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert "total_deals" in data
|
||||||
|
assert data["total_deals"] > 0
|
||||||
|
assert "deals" in data
|
||||||
|
|
||||||
|
def test_get_deals_by_radius_works(self):
|
||||||
|
"""Smoke test: Can fetch polygon metadata by radius."""
|
||||||
|
# Use small radius for speed
|
||||||
|
result = get_deals_by_radius(self.TEST_LAT, self.TEST_LON, radius_meters=100)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert "total_polygons" in data
|
||||||
|
assert data["total_polygons"] > 0
|
||||||
|
|
||||||
|
def test_find_recent_deals_minimal(self):
|
||||||
|
"""Smoke test: Main tool works with minimal data."""
|
||||||
|
# Use very small limits to speed up
|
||||||
|
result = find_recent_deals_for_address(
|
||||||
|
self.TEST_ADDRESS,
|
||||||
|
years_back=1,
|
||||||
|
radius_meters=30,
|
||||||
|
max_deals=10
|
||||||
|
)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
# Just verify structure, don't check counts
|
||||||
|
assert "search_parameters" in data
|
||||||
|
assert "market_statistics" in data
|
||||||
|
assert "deals" in data
|
||||||
|
assert isinstance(data["deals"], list)
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
"""
|
||||||
|
End-to-end tests for all MCP tools.
|
||||||
|
|
||||||
|
These tests make real API calls to verify the complete functionality
|
||||||
|
of each MCP tool from end to end.
|
||||||
|
|
||||||
|
Mark as integration tests since they hit real APIs.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import pytest
|
||||||
|
from nadlan_mcp.fastmcp_server import (
|
||||||
|
autocomplete_address,
|
||||||
|
find_recent_deals_for_address,
|
||||||
|
analyze_market_trends,
|
||||||
|
get_valuation_comparables,
|
||||||
|
get_deal_statistics,
|
||||||
|
get_market_activity_metrics,
|
||||||
|
compare_addresses,
|
||||||
|
get_street_deals,
|
||||||
|
get_neighborhood_deals,
|
||||||
|
get_deals_by_radius,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
class TestMCPToolsE2E:
|
||||||
|
"""End-to-end tests for all 10 MCP tools."""
|
||||||
|
|
||||||
|
# Test addresses
|
||||||
|
TEST_ADDRESS_1 = "סוקולוב 38 חולון"
|
||||||
|
TEST_ADDRESS_2 = "דיזנגוף 50 תל אביב"
|
||||||
|
TEST_POLYGON_ID = "52385050" # Known polygon with data
|
||||||
|
# ITM coordinates for Holon (lat, lon format for MCP tool)
|
||||||
|
TEST_LAT = 3766290.19
|
||||||
|
TEST_LON = 3870928.84
|
||||||
|
|
||||||
|
def test_autocomplete_address(self):
|
||||||
|
"""Test address autocomplete returns results."""
|
||||||
|
result = autocomplete_address("חולון סוקולוב")
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert isinstance(data, list)
|
||||||
|
assert len(data) > 0
|
||||||
|
|
||||||
|
# Check first result has expected structure
|
||||||
|
first = data[0]
|
||||||
|
assert "text" in first
|
||||||
|
assert "coordinates" in first or "id" in first
|
||||||
|
|
||||||
|
def test_find_recent_deals_for_address(self):
|
||||||
|
"""Test finding recent deals for an address."""
|
||||||
|
result = find_recent_deals_for_address(self.TEST_ADDRESS_1, max_deals=100)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
# Check response structure
|
||||||
|
assert "search_parameters" in data
|
||||||
|
assert "market_statistics" in data
|
||||||
|
assert "deals" in data
|
||||||
|
|
||||||
|
# Verify some deals were found
|
||||||
|
assert isinstance(data["deals"], list)
|
||||||
|
assert len(data["deals"]) > 0
|
||||||
|
|
||||||
|
# Check deal structure
|
||||||
|
deal = data["deals"][0]
|
||||||
|
assert "deal_amount" in deal
|
||||||
|
assert "deal_date" in deal
|
||||||
|
assert "settlement_name_heb" in deal
|
||||||
|
|
||||||
|
def test_analyze_market_trends(self):
|
||||||
|
"""Test market trend analysis."""
|
||||||
|
result = analyze_market_trends(
|
||||||
|
self.TEST_ADDRESS_1, years_back=3, radius_meters=100
|
||||||
|
)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
# Check response structure
|
||||||
|
assert "market_summary" in data
|
||||||
|
assert "total_deals" in data["market_summary"]
|
||||||
|
assert isinstance(data["market_summary"]["total_deals"], int)
|
||||||
|
assert data["market_summary"]["total_deals"] >= 0
|
||||||
|
|
||||||
|
def test_get_valuation_comparables(self):
|
||||||
|
"""Test getting valuation comparables."""
|
||||||
|
result = get_valuation_comparables(
|
||||||
|
self.TEST_ADDRESS_1,
|
||||||
|
years_back=3,
|
||||||
|
min_rooms=3.0,
|
||||||
|
max_rooms=5.0
|
||||||
|
)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert "total_comparables" in data
|
||||||
|
assert isinstance(data["total_comparables"], int)
|
||||||
|
assert data["total_comparables"] >= 0
|
||||||
|
|
||||||
|
if data["total_comparables"] > 0:
|
||||||
|
assert "comparables" in data
|
||||||
|
comp = data["comparables"][0]
|
||||||
|
assert "deal_amount" in comp
|
||||||
|
assert "asset_room_num" in comp
|
||||||
|
|
||||||
|
def test_get_deal_statistics(self):
|
||||||
|
"""Test deal statistics calculation."""
|
||||||
|
result = get_deal_statistics(self.TEST_ADDRESS_1, years_back=3)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
# Check response structure
|
||||||
|
assert "statistics" in data
|
||||||
|
if "sample_size" in data["statistics"]:
|
||||||
|
assert isinstance(data["statistics"]["sample_size"], int)
|
||||||
|
assert data["statistics"]["sample_size"] >= 0
|
||||||
|
|
||||||
|
def test_get_market_activity_metrics(self):
|
||||||
|
"""Test market activity metrics."""
|
||||||
|
result = get_market_activity_metrics(self.TEST_ADDRESS_1, years_back=3)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
# Check response structure
|
||||||
|
assert "market_activity" in data
|
||||||
|
if data["market_activity"] is not None:
|
||||||
|
assert "activity_score" in data["market_activity"]
|
||||||
|
|
||||||
|
def test_compare_addresses(self):
|
||||||
|
"""Test comparing multiple addresses."""
|
||||||
|
result = compare_addresses([self.TEST_ADDRESS_1, self.TEST_ADDRESS_2])
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
assert "addresses_compared" in data
|
||||||
|
assert "all_results" in data
|
||||||
|
|
||||||
|
def test_get_street_deals(self):
|
||||||
|
"""Test getting street-level deals."""
|
||||||
|
result = get_street_deals(self.TEST_POLYGON_ID, limit=100, deal_type=2)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert "total_deals" in data
|
||||||
|
assert isinstance(data["total_deals"], int)
|
||||||
|
assert data["total_deals"] > 0 # Known polygon should have deals
|
||||||
|
|
||||||
|
assert "deals" in data
|
||||||
|
assert len(data["deals"]) > 0
|
||||||
|
|
||||||
|
# Check deal structure
|
||||||
|
deal = data["deals"][0]
|
||||||
|
assert "deal_amount" in deal
|
||||||
|
assert "deal_date" in deal
|
||||||
|
|
||||||
|
def test_get_neighborhood_deals(self):
|
||||||
|
"""Test getting neighborhood-level deals."""
|
||||||
|
result = get_neighborhood_deals(self.TEST_POLYGON_ID, limit=100, deal_type=2)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert "total_deals" in data
|
||||||
|
assert isinstance(data["total_deals"], int)
|
||||||
|
assert data["total_deals"] > 0 # Known polygon should have deals
|
||||||
|
|
||||||
|
assert "deals" in data
|
||||||
|
assert len(data["deals"]) > 0
|
||||||
|
|
||||||
|
def test_get_deals_by_radius(self):
|
||||||
|
"""Test getting polygon metadata by radius."""
|
||||||
|
result = get_deals_by_radius(self.TEST_LAT, self.TEST_LON, radius_meters=500)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert "total_polygons" in data
|
||||||
|
assert isinstance(data["total_polygons"], int)
|
||||||
|
assert data["total_polygons"] > 0 # Known coords should have polygons
|
||||||
|
|
||||||
|
assert "polygons" in data
|
||||||
|
assert len(data["polygons"]) > 0
|
||||||
|
|
||||||
|
# Check polygon metadata structure
|
||||||
|
polygon = data["polygons"][0]
|
||||||
|
assert "polygon_id" in polygon or "objectid" in polygon
|
||||||
|
|
||||||
|
def test_get_deals_by_radius_no_results(self):
|
||||||
|
"""Test get_deals_by_radius with coords that have no data."""
|
||||||
|
# Use coordinates unlikely to have data
|
||||||
|
result = get_deals_by_radius(37.66, 38.71, radius_meters=10)
|
||||||
|
|
||||||
|
# Should return a message string, not JSON
|
||||||
|
assert isinstance(result, str)
|
||||||
|
assert "No polygons found" in result or "total_polygons" in result
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"text": "סוקולוב חולון",
|
||||||
|
"id": "street|STREET_MID_POINT|40087",
|
||||||
|
"type": "street",
|
||||||
|
"score": 1658.0989,
|
||||||
|
"coordinates": {
|
||||||
|
"longitude": 3871467.9873431968,
|
||||||
|
"latitude": 3766276.9742487124
|
||||||
|
},
|
||||||
|
"shape": "POINT(3871467.9873431968 3766276.9742487124)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "קופת חולים - לאומית סוקולוב חולון",
|
||||||
|
"id": "institutes|POI_BLDG|71871",
|
||||||
|
"type": "institutes",
|
||||||
|
"score": 1095.5675,
|
||||||
|
"coordinates": {
|
||||||
|
"longitude": 3871143.6159368134,
|
||||||
|
"latitude": 3766329.3192062005
|
||||||
|
},
|
||||||
|
"shape": "POINT(3871143.6159368134 3766329.3192062005)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "קופת חולים - לאומית סוקולוב חולון",
|
||||||
|
"id": "poi|POI_MID_POINT|102233",
|
||||||
|
"type": "poi",
|
||||||
|
"score": 1099.5448,
|
||||||
|
"coordinates": {
|
||||||
|
"longitude": 3871143.6159681133,
|
||||||
|
"latitude": 3766329.319199102
|
||||||
|
},
|
||||||
|
"shape": "POINT(3871143.6159681133 3766329.319199102)"
|
||||||
|
}
|
||||||
|
]
|
||||||
+332
@@ -0,0 +1,332 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"objectid": 1969420,
|
||||||
|
"deal_amount": 1600000.0,
|
||||||
|
"deal_date": "2025-09-11",
|
||||||
|
"asset_area": 61.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870720.4640780594 3766261.471274853,3870704.822454856 3766280.494942658,3870715.8743646624 3766289.5618421077,3870731.3745853226 3766270.5375762666,3870720.4640780594 3766261.471274853)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000721,
|
||||||
|
"streetNameHeb": "בילינסון",
|
||||||
|
"streetNameEng": "Belinson",
|
||||||
|
"houseNum": 6,
|
||||||
|
"floorNo": "רביעית",
|
||||||
|
"dealId": 3633677206,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 3,
|
||||||
|
"gushNum": 7172,
|
||||||
|
"parcelNum": 235,
|
||||||
|
"subParcelNum": 11,
|
||||||
|
"polygonId": "52995209",
|
||||||
|
"price_per_sqm": 26229.51
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1968034,
|
||||||
|
"deal_amount": 1800000.0,
|
||||||
|
"deal_date": "2025-08-21",
|
||||||
|
"asset_area": 51.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3871546.267254118 3766046.2332329005,3871519.7450213963 3766048.8632871644,3871520.755660357 3766058.564386736,3871529.006896269 3766057.780093892,3871529.5742626246 3766063.3353820755,3871547.727459817 3766061.4891484776,3871546.267254118 3766046.2332329005)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000415,
|
||||||
|
"streetNameHeb": "חנקין",
|
||||||
|
"streetNameEng": "Hankin",
|
||||||
|
"houseNum": 62,
|
||||||
|
"floorNo": "שניה",
|
||||||
|
"dealId": 3632991008,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 3,
|
||||||
|
"gushNum": 7170,
|
||||||
|
"parcelNum": 91,
|
||||||
|
"subParcelNum": 8,
|
||||||
|
"polygonId": "52487416",
|
||||||
|
"price_per_sqm": 35294.12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1972678,
|
||||||
|
"deal_amount": 2170000.0,
|
||||||
|
"deal_date": "2025-08-20",
|
||||||
|
"asset_area": 90.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870461.085939886 3765412.661079392,3870461.8556948095 3765419.4469509623,3870454.5119660264 3765416.0582189877,3870449.8311605644 3765416.7315218183,3870448.8832249534 3765412.6281236825,3870444.1110105794 3765413.266600847,3870448.3403304657 3765432.7404882894,3870508.9368100218 3765424.106078474,3870506.5193002103 3765406.631910944,3870482.4682015995 3765409.8236224824,3870476.137660146 3765410.663707863,3870461.085939886 3765412.661079392)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000720,
|
||||||
|
"streetNameHeb": "לאון בלום",
|
||||||
|
"streetNameEng": "L?on Blum",
|
||||||
|
"houseNum": 64,
|
||||||
|
"floorNo": "שניה",
|
||||||
|
"dealId": 3633156134,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 4,
|
||||||
|
"gushNum": 7176,
|
||||||
|
"parcelNum": 9,
|
||||||
|
"subParcelNum": 59,
|
||||||
|
"polygonId": "52202717",
|
||||||
|
"price_per_sqm": 24111.11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1967412,
|
||||||
|
"deal_amount": 2050000.0,
|
||||||
|
"deal_date": "2025-08-14",
|
||||||
|
"asset_area": 95.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3871347.8555616946 3766162.0722470563,3871334.99564493 3766163.205144352,3871336.9037298146 3766187.3432294633,3871349.869159977 3766186.35283101,3871347.8555616946 3766162.0722470563)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000443,
|
||||||
|
"streetNameHeb": "שפרינצק",
|
||||||
|
"streetNameEng": "Sprinzak",
|
||||||
|
"houseNum": 29,
|
||||||
|
"floorNo": "שניה",
|
||||||
|
"dealId": 3632926686,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 4,
|
||||||
|
"gushNum": 7170,
|
||||||
|
"parcelNum": 28,
|
||||||
|
"subParcelNum": 6,
|
||||||
|
"polygonId": "53339618",
|
||||||
|
"price_per_sqm": 21578.95
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1972438,
|
||||||
|
"deal_amount": 2057000.0,
|
||||||
|
"deal_date": "2025-08-13",
|
||||||
|
"asset_area": 70.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870628.707375865 3765928.3916899217,3870616.197248105 3765933.4442600156,3870624.479632951 3765954.33986368,3870628.9524073373 3765952.58190368,3870629.4304386983 3765953.8507194677,3870634.044605639 3765952.0933281765,3870632.98273369 3765949.4960702136,3870635.237077585 3765948.5461212373,3870632.1913898066 3765941.121957529,3870633.501643529 3765940.5352417007,3870628.707375865 3765928.3916899217)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000720,
|
||||||
|
"streetNameHeb": "לאון בלום",
|
||||||
|
"streetNameEng": "L?on Blum",
|
||||||
|
"houseNum": 23,
|
||||||
|
"floorNo": null,
|
||||||
|
"dealId": 3633759218,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 3,
|
||||||
|
"gushNum": 7176,
|
||||||
|
"parcelNum": 23,
|
||||||
|
"subParcelNum": 2,
|
||||||
|
"polygonId": "52475840",
|
||||||
|
"price_per_sqm": 29385.71
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1972437,
|
||||||
|
"deal_amount": 2207000.0,
|
||||||
|
"deal_date": "2025-08-13",
|
||||||
|
"asset_area": 78.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870628.707375865 3765928.3916899217,3870616.197248105 3765933.4442600156,3870624.479632951 3765954.33986368,3870628.9524073373 3765952.58190368,3870629.4304386983 3765953.8507194677,3870634.044605639 3765952.0933281765,3870632.98273369 3765949.4960702136,3870635.237077585 3765948.5461212373,3870632.1913898066 3765941.121957529,3870633.501643529 3765940.5352417007,3870628.707375865 3765928.3916899217)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000720,
|
||||||
|
"streetNameHeb": "לאון בלום",
|
||||||
|
"streetNameEng": "L?on Blum",
|
||||||
|
"houseNum": 23,
|
||||||
|
"floorNo": null,
|
||||||
|
"dealId": 3633751330,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 4,
|
||||||
|
"gushNum": 7176,
|
||||||
|
"parcelNum": 23,
|
||||||
|
"subParcelNum": 2,
|
||||||
|
"polygonId": "52475840",
|
||||||
|
"price_per_sqm": 28294.87
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1967707,
|
||||||
|
"deal_amount": 1400000.0,
|
||||||
|
"deal_date": "2025-08-12",
|
||||||
|
"asset_area": 74.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3871406.5046315272 3766032.4932356784,3871393.876109203 3766033.069669732,3871395.3254547855 3766060.893880203,3871408.6494925357 3766060.5448129214,3871406.5046315272 3766032.4932356784)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000503,
|
||||||
|
"streetNameHeb": "ביאליק",
|
||||||
|
"streetNameEng": "Bialik",
|
||||||
|
"houseNum": 8,
|
||||||
|
"floorNo": "שניה",
|
||||||
|
"dealId": 3633004406,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 3,
|
||||||
|
"gushNum": 7170,
|
||||||
|
"parcelNum": 57,
|
||||||
|
"subParcelNum": 6,
|
||||||
|
"polygonId": "64907781",
|
||||||
|
"price_per_sqm": 18918.92
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1971495,
|
||||||
|
"deal_amount": 1000000.0,
|
||||||
|
"deal_date": "2025-08-07",
|
||||||
|
"asset_area": 82.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3871176.244711883 3765574.7175676874,3871108.214092787 3765583.100809277,3871109.35308285 3765593.027038448,3871177.5128026525 3765584.7863754295,3871176.244711883 3765574.7175676874)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "HOLON",
|
||||||
|
"streetCode": 66000304,
|
||||||
|
"streetNameHeb": "ההסתדרות",
|
||||||
|
"streetNameEng": "HaHistadrut",
|
||||||
|
"houseNum": 60,
|
||||||
|
"floorNo": "ראשונה",
|
||||||
|
"dealId": 3632654898,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 4,
|
||||||
|
"gushNum": 7174,
|
||||||
|
"parcelNum": 199,
|
||||||
|
"subParcelNum": 27,
|
||||||
|
"polygonId": "52506738",
|
||||||
|
"price_per_sqm": 12195.12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1969515,
|
||||||
|
"deal_amount": 3000000.0,
|
||||||
|
"deal_date": "2025-08-06",
|
||||||
|
"asset_area": 117.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870582.7778358534 3766007.818947901,3870555.75388674 3766020.9588606944,3870560.5454790723 3766030.81737579,3870587.5814648867 3766017.6182896625,3870582.7778358534 3766007.818947901)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000721,
|
||||||
|
"streetNameHeb": "בילינסון",
|
||||||
|
"streetNameEng": "Belinson",
|
||||||
|
"houseNum": 35,
|
||||||
|
"floorNo": "מרתף+",
|
||||||
|
"dealId": 3632769506,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 5,
|
||||||
|
"gushNum": 7172,
|
||||||
|
"parcelNum": 55,
|
||||||
|
"subParcelNum": 2,
|
||||||
|
"polygonId": "52469890",
|
||||||
|
"price_per_sqm": 25641.03
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1967553,
|
||||||
|
"deal_amount": 3540000.0,
|
||||||
|
"deal_date": "2025-08-04",
|
||||||
|
"asset_area": 130.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3871283.98382443 3766041.272967665,3871272.3244377715 3766042.777610746,3871274.3714343957 3766058.5450168313,3871278.7452240535 3766057.970421447,3871282.8507001335 3766089.5882179793,3871278.6182943583 3766090.1633791463,3871280.9063065085 3766107.565754848,3871297.410878644 3766105.4883675845,3871296.839837455 3766100.8920923057,3871290.697705125 3766101.684622215,3871289.325197258 3766091.1650558305,3871293.4512298624 3766090.672348892,3871291.964111744 3766079.3353630104,3871299.8747903355 3766078.2657070043,3871298.260388772 3766066.3362180092,3871291.1870977194 3766067.2079217676,3871289.8131462242 3766057.05542465,3871286.0290623982 3766057.490289319,3871283.98382443 3766041.272967665)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": null,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000708,
|
||||||
|
"streetNameHeb": "שנקר",
|
||||||
|
"streetNameEng": "Shenkar",
|
||||||
|
"houseNum": 55,
|
||||||
|
"floorNo": "שניה",
|
||||||
|
"dealId": 3632695759,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 5,
|
||||||
|
"gushNum": 7170,
|
||||||
|
"parcelNum": 44,
|
||||||
|
"subParcelNum": 19,
|
||||||
|
"polygonId": "52507812",
|
||||||
|
"price_per_sqm": 27230.77
|
||||||
|
}
|
||||||
|
]
|
||||||
Vendored
+82
@@ -0,0 +1,82 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"dealscount": "10",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7170-31",
|
||||||
|
"objectid": 14248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dealscount": "6",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7172-19",
|
||||||
|
"objectid": 14249
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dealscount": "1",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7172-3",
|
||||||
|
"objectid": 14301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dealscount": "14",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7172-212",
|
||||||
|
"objectid": 14302
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dealscount": "14",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7172-213",
|
||||||
|
"objectid": 14303
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dealscount": "57",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7172-128",
|
||||||
|
"objectid": 14304
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dealscount": "1",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7172-5",
|
||||||
|
"objectid": 14305
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dealscount": "3",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7172-216",
|
||||||
|
"objectid": 14306
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dealscount": "11",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7172-218",
|
||||||
|
"objectid": 14314
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dealscount": "1",
|
||||||
|
"settlementNameHeb": "חולון",
|
||||||
|
"streetNameHeb": null,
|
||||||
|
"houseNum": null,
|
||||||
|
"polygon_id": "7172-7",
|
||||||
|
"objectid": 14315
|
||||||
|
}
|
||||||
|
]
|
||||||
Vendored
+332
@@ -0,0 +1,332 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"objectid": 1960644,
|
||||||
|
"deal_amount": 200000.0,
|
||||||
|
"deal_date": "2024-10-13",
|
||||||
|
"asset_area": 20.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "בנין",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 32,
|
||||||
|
"floorNo": "קרקע",
|
||||||
|
"dealId": 3605441588,
|
||||||
|
"dealNatureDescription": "חנות",
|
||||||
|
"assetRoomNum": null,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 141,
|
||||||
|
"subParcelNum": 5,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 10000.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1960654,
|
||||||
|
"deal_amount": 892000.0,
|
||||||
|
"deal_date": "2024-08-08",
|
||||||
|
"asset_area": 69.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 34,
|
||||||
|
"floorNo": "שניה",
|
||||||
|
"dealId": 3603340277,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 3,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 142,
|
||||||
|
"subParcelNum": 8,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 12927.54
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1960646,
|
||||||
|
"deal_amount": 1650000.0,
|
||||||
|
"deal_date": "2022-08-17",
|
||||||
|
"asset_area": 59.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 32,
|
||||||
|
"floorNo": "ראשונה",
|
||||||
|
"dealId": 3547500052,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 2,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 141,
|
||||||
|
"subParcelNum": 7,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 27966.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1960650,
|
||||||
|
"deal_amount": 500000.0,
|
||||||
|
"deal_date": "2019-03-03",
|
||||||
|
"asset_area": 29.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "בנין",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 34,
|
||||||
|
"floorNo": "קרקע",
|
||||||
|
"dealId": 3453745430,
|
||||||
|
"dealNatureDescription": "חנות",
|
||||||
|
"assetRoomNum": null,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 142,
|
||||||
|
"subParcelNum": 4,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 17241.38
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1960640,
|
||||||
|
"deal_amount": 510000.0,
|
||||||
|
"deal_date": "2016-12-06",
|
||||||
|
"asset_area": 98.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "בנין",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 32,
|
||||||
|
"floorNo": "מרתף",
|
||||||
|
"dealId": 3375045810,
|
||||||
|
"dealNatureDescription": "מחסנים",
|
||||||
|
"assetRoomNum": null,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 141,
|
||||||
|
"subParcelNum": 1,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 5204.08
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1960642,
|
||||||
|
"deal_amount": 899000.0,
|
||||||
|
"deal_date": "2013-12-30",
|
||||||
|
"asset_area": 42.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 32,
|
||||||
|
"floorNo": "שלישית",
|
||||||
|
"dealId": 3306035061,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 2,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 141,
|
||||||
|
"subParcelNum": 11,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 21404.76
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1960638,
|
||||||
|
"deal_amount": 580000.0,
|
||||||
|
"deal_date": "2013-03-11",
|
||||||
|
"asset_area": 47.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "בנין",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 32,
|
||||||
|
"floorNo": "מרתף",
|
||||||
|
"dealId": 3280438878,
|
||||||
|
"dealNatureDescription": "תעשיה",
|
||||||
|
"assetRoomNum": null,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 141,
|
||||||
|
"subParcelNum": 1,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 12340.43
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1960649,
|
||||||
|
"deal_amount": 720000.0,
|
||||||
|
"deal_date": "2008-11-20",
|
||||||
|
"asset_area": 20.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "בנין",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 34,
|
||||||
|
"floorNo": "קרקע",
|
||||||
|
"dealId": 3140497058,
|
||||||
|
"dealNatureDescription": "חנות",
|
||||||
|
"assetRoomNum": null,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 142,
|
||||||
|
"subParcelNum": 3,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 36000.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1960643,
|
||||||
|
"deal_amount": 100000.0,
|
||||||
|
"deal_date": "2008-11-09",
|
||||||
|
"asset_area": 58.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 32,
|
||||||
|
"floorNo": "שלישית",
|
||||||
|
"dealId": 3139951568,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 3,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 141,
|
||||||
|
"subParcelNum": 12,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 1724.14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectid": 1960647,
|
||||||
|
"deal_amount": 430000.0,
|
||||||
|
"deal_date": "2008-06-17",
|
||||||
|
"asset_area": 42.0,
|
||||||
|
"settlement_name_heb": "חולון",
|
||||||
|
"property_type_description": "דירה",
|
||||||
|
"neighborhood": "קרית עבודה",
|
||||||
|
"street_name": null,
|
||||||
|
"house_number": null,
|
||||||
|
"floor": null,
|
||||||
|
"floor_number": null,
|
||||||
|
"rooms": null,
|
||||||
|
"priority": null,
|
||||||
|
"shape": "MULTIPOLYGON(((3870903.639498367 3766273.3642689525,3870887.6103521264 3766277.301987773,3870893.7433254845 3766302.5584789426,3870909.784521754 3766298.561590301,3870903.639498367 3766273.3642689525)))",
|
||||||
|
"source_polygon_id": null,
|
||||||
|
"sourceorder": 1,
|
||||||
|
"settlementId": 6600,
|
||||||
|
"settlementNameEng": "Holon",
|
||||||
|
"streetCode": 66000401,
|
||||||
|
"streetNameHeb": "סוקולוב",
|
||||||
|
"streetNameEng": "Sokolov",
|
||||||
|
"houseNum": 32,
|
||||||
|
"floorNo": "שניה",
|
||||||
|
"dealId": 3137311234,
|
||||||
|
"dealNatureDescription": "דירה בבית קומות",
|
||||||
|
"assetRoomNum": 2,
|
||||||
|
"gushNum": 7166,
|
||||||
|
"parcelNum": 141,
|
||||||
|
"subParcelNum": 9,
|
||||||
|
"polygonId": "52385050",
|
||||||
|
"price_per_sqm": 10238.1
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
"""
|
||||||
|
Fast unit tests for MCP tools using cached fixtures.
|
||||||
|
|
||||||
|
These tests use pre-recorded API responses to run quickly (~1s)
|
||||||
|
without hitting the real Govmap API.
|
||||||
|
|
||||||
|
For full E2E tests with real API calls, see tests/e2e/test_mcp_tools.py
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch, Mock
|
||||||
|
from nadlan_mcp.govmap.models import Deal, AutocompleteResult, AutocompleteResponse, CoordinatePoint
|
||||||
|
from nadlan_mcp.fastmcp_server import (
|
||||||
|
autocomplete_address,
|
||||||
|
find_recent_deals_for_address,
|
||||||
|
get_street_deals,
|
||||||
|
get_neighborhood_deals,
|
||||||
|
get_deals_by_radius,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Load fixtures
|
||||||
|
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
||||||
|
|
||||||
|
|
||||||
|
def load_fixture(filename):
|
||||||
|
"""Load a JSON fixture file."""
|
||||||
|
with open(FIXTURES_DIR / filename, encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_autocomplete_data():
|
||||||
|
"""Cached autocomplete response."""
|
||||||
|
data = load_fixture("autocomplete_response.json")
|
||||||
|
results = [AutocompleteResult.model_validate(r) for r in data]
|
||||||
|
return AutocompleteResponse(results=results, resultsCount=len(results))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_street_deals_data():
|
||||||
|
"""Cached street deals response."""
|
||||||
|
data = load_fixture("street_deals.json")
|
||||||
|
return [Deal.model_validate(d) for d in data]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_neighborhood_deals_data():
|
||||||
|
"""Cached neighborhood deals response."""
|
||||||
|
data = load_fixture("neighborhood_deals.json")
|
||||||
|
return [Deal.model_validate(d) for d in data]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_polygon_metadata_data():
|
||||||
|
"""Cached polygon metadata response."""
|
||||||
|
return load_fixture("polygon_metadata.json")
|
||||||
|
|
||||||
|
|
||||||
|
class TestMCPToolsFast:
|
||||||
|
"""Fast unit tests for MCP tools using mocked data."""
|
||||||
|
|
||||||
|
@patch('nadlan_mcp.fastmcp_server.client')
|
||||||
|
def test_autocomplete_address_fast(self, mock_client, mock_autocomplete_data):
|
||||||
|
"""Test autocomplete with cached data."""
|
||||||
|
mock_client.autocomplete_address.return_value = mock_autocomplete_data
|
||||||
|
|
||||||
|
result = autocomplete_address("חולון סוקולוב")
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert isinstance(data, list)
|
||||||
|
assert len(data) > 0
|
||||||
|
assert "text" in data[0]
|
||||||
|
assert "coordinates" in data[0]
|
||||||
|
|
||||||
|
@patch('nadlan_mcp.fastmcp_server.client')
|
||||||
|
def test_get_street_deals_fast(self, mock_client, mock_street_deals_data):
|
||||||
|
"""Test street deals with cached data."""
|
||||||
|
mock_client.get_street_deals.return_value = mock_street_deals_data
|
||||||
|
|
||||||
|
result = get_street_deals("52385050", limit=100, deal_type=2)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert "total_deals" in data
|
||||||
|
assert data["total_deals"] == len(mock_street_deals_data)
|
||||||
|
assert "deals" in data
|
||||||
|
assert len(data["deals"]) > 0
|
||||||
|
|
||||||
|
# Verify deal structure
|
||||||
|
deal = data["deals"][0]
|
||||||
|
assert "deal_amount" in deal
|
||||||
|
assert "deal_date" in deal
|
||||||
|
|
||||||
|
@patch('nadlan_mcp.fastmcp_server.client')
|
||||||
|
def test_get_neighborhood_deals_fast(self, mock_client, mock_neighborhood_deals_data):
|
||||||
|
"""Test neighborhood deals with cached data."""
|
||||||
|
mock_client.get_neighborhood_deals.return_value = mock_neighborhood_deals_data
|
||||||
|
|
||||||
|
result = get_neighborhood_deals("52385050", limit=100, deal_type=2)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert "total_deals" in data
|
||||||
|
assert data["total_deals"] == len(mock_neighborhood_deals_data)
|
||||||
|
assert "deals" in data
|
||||||
|
|
||||||
|
@patch('nadlan_mcp.fastmcp_server.client')
|
||||||
|
def test_get_deals_by_radius_fast(self, mock_client, mock_polygon_metadata_data):
|
||||||
|
"""Test deals by radius with cached data."""
|
||||||
|
mock_client.get_deals_by_radius.return_value = mock_polygon_metadata_data
|
||||||
|
|
||||||
|
result = get_deals_by_radius(37.6629, 38.7093, radius_meters=500)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
assert "total_polygons" in data
|
||||||
|
assert data["total_polygons"] == len(mock_polygon_metadata_data)
|
||||||
|
assert "polygons" in data
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Complex workflow with statistics - tested in E2E suite")
|
||||||
|
@patch('nadlan_mcp.fastmcp_server.client')
|
||||||
|
def test_find_recent_deals_fast(self, mock_client, mock_street_deals_data,
|
||||||
|
mock_neighborhood_deals_data, mock_polygon_metadata_data,
|
||||||
|
mock_autocomplete_data):
|
||||||
|
"""Test find_recent_deals with fully mocked workflow.
|
||||||
|
|
||||||
|
NOTE: This test is skipped because find_recent_deals_for_address has complex
|
||||||
|
statistics calculations that require specific data structure. Use E2E tests
|
||||||
|
in tests/e2e/test_mcp_tools.py for full integration testing.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@patch('nadlan_mcp.fastmcp_server.client')
|
||||||
|
def test_no_deals_found(self, mock_client):
|
||||||
|
"""Test handling when no deals are found."""
|
||||||
|
mock_client.get_street_deals.return_value = []
|
||||||
|
|
||||||
|
result = get_street_deals("999999", limit=100, deal_type=2)
|
||||||
|
|
||||||
|
# Should return a message string when no deals found
|
||||||
|
assert isinstance(result, str)
|
||||||
|
assert "No" in result or "found" in result.lower()
|
||||||
|
|
||||||
|
@patch('nadlan_mcp.fastmcp_server.client')
|
||||||
|
def test_deal_type_filtering(self, mock_client, mock_street_deals_data):
|
||||||
|
"""Test that deal_type parameter is passed correctly."""
|
||||||
|
mock_client.get_street_deals.return_value = mock_street_deals_data
|
||||||
|
|
||||||
|
# Test with deal_type=1 (first hand)
|
||||||
|
result = get_street_deals("52385050", limit=100, deal_type=1)
|
||||||
|
data = json.loads(result)
|
||||||
|
|
||||||
|
# Verify client was called with correct deal_type
|
||||||
|
mock_client.get_street_deals.assert_called_once_with("52385050", 100, deal_type=1)
|
||||||
|
assert "deal_type" in data
|
||||||
|
assert data["deal_type"] == 1
|
||||||
Reference in New Issue
Block a user