Ruff fixes

This commit is contained in:
Nitzan Pomerantz
2025-10-30 22:24:40 +02:00
parent a80b38047c
commit e4aa6487ff
46 changed files with 1563 additions and 1062 deletions
+43 -32
View File
@@ -5,6 +5,7 @@ Comprehensive tests for filter_deals_by_criteria function.
"""
import pytest
from nadlan_mcp.govmap.filters import filter_deals_by_criteria
from nadlan_mcp.govmap.models import Deal, DealFilters
@@ -80,83 +81,79 @@ class TestFilterDealsByCriteria:
result = filter_deals_by_criteria(sample_deals, property_type="דירה")
# Should match "דירה" and "דירת גג" via substring match.
assert len(result) == 3
assert set(d.objectid for d in result) == {1, 2, 4}
assert {d.objectid for d in result} == {1, 2, 4}
def test_filter_by_min_rooms(self, sample_deals):
"""Test filtering by minimum rooms."""
result = filter_deals_by_criteria(sample_deals, min_rooms=4.0)
assert len(result) == 3
assert set(d.objectid for d in result) == {2, 3, 5}
assert {d.objectid for d in result} == {2, 3, 5}
def test_filter_by_max_rooms(self, sample_deals):
"""Test filtering by maximum rooms."""
result = filter_deals_by_criteria(sample_deals, max_rooms=3.0)
assert len(result) == 2
assert set(d.objectid for d in result) == {1, 4}
assert {d.objectid for d in result} == {1, 4}
def test_filter_by_room_range(self, sample_deals):
"""Test filtering by room range."""
result = filter_deals_by_criteria(sample_deals, min_rooms=3.0, max_rooms=4.0)
assert len(result) == 2
assert set(d.objectid for d in result) == {1, 2}
assert {d.objectid for d in result} == {1, 2}
def test_filter_by_min_price(self, sample_deals):
"""Test filtering by minimum price."""
result = filter_deals_by_criteria(sample_deals, min_price=1200000.0)
assert len(result) == 3
assert set(d.objectid for d in result) == {2, 3, 5}
assert {d.objectid for d in result} == {2, 3, 5}
def test_filter_by_max_price(self, sample_deals):
"""Test filtering by maximum price."""
result = filter_deals_by_criteria(sample_deals, max_price=1000000.0)
assert len(result) == 2
assert set(d.objectid for d in result) == {1, 4}
assert {d.objectid for d in result} == {1, 4}
def test_filter_by_price_range(self, sample_deals):
"""Test filtering by price range."""
result = filter_deals_by_criteria(
sample_deals, min_price=1000000.0, max_price=1500000.0
)
result = filter_deals_by_criteria(sample_deals, min_price=1000000.0, max_price=1500000.0)
assert len(result) == 3
assert set(d.objectid for d in result) == {1, 2, 5}
assert {d.objectid for d in result} == {1, 2, 5}
def test_filter_by_min_area(self, sample_deals):
"""Test filtering by minimum area."""
result = filter_deals_by_criteria(sample_deals, min_area=100.0)
assert len(result) == 3
assert set(d.objectid for d in result) == {2, 3, 5}
assert {d.objectid for d in result} == {2, 3, 5}
def test_filter_by_max_area(self, sample_deals):
"""Test filtering by maximum area."""
result = filter_deals_by_criteria(sample_deals, max_area=80.0)
assert len(result) == 2
assert set(d.objectid for d in result) == {1, 4}
assert {d.objectid for d in result} == {1, 4}
def test_filter_by_area_range(self, sample_deals):
"""Test filtering by area range."""
result = filter_deals_by_criteria(
sample_deals, min_area=80.0, max_area=120.0
)
result = filter_deals_by_criteria(sample_deals, min_area=80.0, max_area=120.0)
assert len(result) == 3
assert set(d.objectid for d in result) == {1, 2, 5}
assert {d.objectid for d in result} == {1, 2, 5}
def test_filter_by_min_floor(self, sample_deals):
"""Test filtering by minimum floor."""
result = filter_deals_by_criteria(sample_deals, min_floor=2)
assert len(result) == 3
assert set(d.objectid for d in result) == {1, 2, 5}
assert {d.objectid for d in result} == {1, 2, 5}
def test_filter_by_max_floor(self, sample_deals):
"""Test filtering by maximum floor."""
result = filter_deals_by_criteria(sample_deals, max_floor=2)
assert len(result) == 3
assert set(d.objectid for d in result) == {1, 3, 4}
assert {d.objectid for d in result} == {1, 3, 4}
def test_filter_by_floor_range(self, sample_deals):
"""Test filtering by floor range."""
result = filter_deals_by_criteria(sample_deals, min_floor=1, max_floor=5)
assert len(result) == 3
assert set(d.objectid for d in result) == {1, 2, 4}
assert {d.objectid for d in result} == {1, 2, 4}
def test_combined_filters(self, sample_deals):
"""Test combining multiple filters."""
@@ -171,19 +168,17 @@ class TestFilterDealsByCriteria:
# objectid=1: "דירה", 3 rooms, 1M price ✓
# objectid=2: "דירת גג" (matches "דירה" variant), 4 rooms, 1.5M price ✓
assert len(result) == 2
assert set(d.objectid for d in result) == {1, 2}
assert {d.objectid for d in result} == {1, 2}
def test_filter_using_dealfilters_model(self, sample_deals):
"""Test filtering using DealFilters model."""
filters = DealFilters(
property_type="דירה", min_rooms=3.0, max_rooms=4.0
)
filters = DealFilters(property_type="דירה", min_rooms=3.0, max_rooms=4.0)
result = filter_deals_by_criteria(sample_deals, filters=filters)
# Should match objectid=1 and objectid=2
# objectid=1: "דירה", 3 rooms ✓
# objectid=2: "דירת גג" (matches "דירה" variant), 4 rooms ✓
assert len(result) == 2
assert set(d.objectid for d in result) == {1, 2}
assert {d.objectid for d in result} == {1, 2}
def test_filter_using_dict(self, sample_deals):
"""Test filtering using dict (converted to DealFilters)."""
@@ -197,23 +192,35 @@ class TestFilterDealsByCriteria:
# objectid=1: "דירה", 3 rooms ✓
# objectid=2: "דירת גג" (matches "דירה" variant), 4 rooms ✓
assert len(result) == 2
assert set(d.objectid for d in result) == {1, 2}
assert {d.objectid for d in result} == {1, 2}
def test_individual_params_override_filters_model(self, sample_deals):
"""Test that individual parameters override filters model."""
filters = DealFilters(min_rooms=5.0) # Would match only objectid=3
result = filter_deals_by_criteria(
sample_deals, filters=filters, min_rooms=3.0 # Override to 3.0
sample_deals,
filters=filters,
min_rooms=3.0, # Override to 3.0
)
# Should use min_rooms=3.0, not 5.0
assert len(result) == 4
assert set(d.objectid for d in result) == {1, 2, 3, 5}
assert {d.objectid for d in result} == {1, 2, 3, 5}
def test_filters_exclude_deals_with_missing_property_type(self):
"""Test that deals with missing property_type are excluded when filter active."""
deals = [
Deal(objectid=1, deal_amount=1000000, deal_date="2024-01-01", property_type_description="דירה"),
Deal(objectid=2, deal_amount=1000000, deal_date="2024-01-01", property_type_description=None),
Deal(
objectid=1,
deal_amount=1000000,
deal_date="2024-01-01",
property_type_description="דירה",
),
Deal(
objectid=2,
deal_amount=1000000,
deal_date="2024-01-01",
property_type_description=None,
),
Deal(objectid=3, deal_amount=1000000, deal_date="2024-01-01"), # No property_type
]
result = filter_deals_by_criteria(deals, property_type="דירה")
@@ -281,13 +288,17 @@ class TestFilterDealsByCriteria:
"""Test floor filtering with Hebrew floor descriptions."""
deals = [
Deal(objectid=1, deal_amount=1000000, deal_date="2024-01-01", floor="קרקע"), # Ground=0
Deal(objectid=2, deal_amount=1000000, deal_date="2024-01-01", floor="קומה 3"), # Floor 3
Deal(objectid=3, deal_amount=1000000, deal_date="2024-01-01", floor="מרתף"), # Basement=-1
Deal(
objectid=2, deal_amount=1000000, deal_date="2024-01-01", floor="קומה 3"
), # Floor 3
Deal(
objectid=3, deal_amount=1000000, deal_date="2024-01-01", floor="מרתף"
), # Basement=-1
]
result = filter_deals_by_criteria(deals, min_floor=0)
# Should match ground (0) and floor 3, but not basement (-1)
assert len(result) == 2
assert set(d.objectid for d in result) == {1, 2}
assert {d.objectid for d in result} == {1, 2}
@pytest.mark.parametrize(
"min_val,max_val,expected_count",
+179 -36
View File
@@ -4,15 +4,17 @@ Tests for nadlan_mcp.govmap.market_analysis module.
Comprehensive tests for market analysis functions.
"""
from datetime import datetime, timedelta
import pytest
from datetime import date, datetime, timedelta
from nadlan_mcp.govmap.market_analysis import (
parse_deal_dates,
calculate_market_activity_score,
analyze_investment_potential,
calculate_market_activity_score,
get_market_liquidity,
parse_deal_dates,
)
from nadlan_mcp.govmap.models import Deal, MarketActivityScore, InvestmentAnalysis, LiquidityMetrics
from nadlan_mcp.govmap.models import Deal, InvestmentAnalysis, LiquidityMetrics, MarketActivityScore
def get_recent_date(months_ago=0, days_ago=0):
@@ -27,11 +29,36 @@ class TestParseDealDates:
def sample_deals(self):
"""Create sample deals spanning multiple months (recent dates)."""
return [
Deal(objectid=1, deal_amount=1000000, deal_date=get_recent_date(months_ago=4, days_ago=15), asset_area=80),
Deal(objectid=2, deal_amount=1100000, deal_date=get_recent_date(months_ago=4, days_ago=10), asset_area=85),
Deal(objectid=3, deal_amount=1200000, deal_date=get_recent_date(months_ago=3, days_ago=20), asset_area=90),
Deal(objectid=4, deal_amount=1300000, deal_date=get_recent_date(months_ago=2, days_ago=25), asset_area=95),
Deal(objectid=5, deal_amount=1400000, deal_date=get_recent_date(months_ago=1, days_ago=18), asset_area=100),
Deal(
objectid=1,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=4, days_ago=15),
asset_area=80,
),
Deal(
objectid=2,
deal_amount=1100000,
deal_date=get_recent_date(months_ago=4, days_ago=10),
asset_area=85,
),
Deal(
objectid=3,
deal_amount=1200000,
deal_date=get_recent_date(months_ago=3, days_ago=20),
asset_area=90,
),
Deal(
objectid=4,
deal_amount=1300000,
deal_date=get_recent_date(months_ago=2, days_ago=25),
asset_area=95,
),
Deal(
objectid=5,
deal_amount=1400000,
deal_date=get_recent_date(months_ago=1, days_ago=18),
asset_area=100,
),
]
def test_parse_deal_dates_basic(self, sample_deals):
@@ -60,8 +87,18 @@ class TestParseDealDates:
"""Test parsing with date objects instead of strings."""
recent = datetime.now().date()
deals = [
Deal(objectid=1, deal_amount=1000000, deal_date=recent - timedelta(days=30), asset_area=80),
Deal(objectid=2, deal_amount=1100000, deal_date=recent - timedelta(days=60), asset_area=85),
Deal(
objectid=1,
deal_amount=1000000,
deal_date=recent - timedelta(days=30),
asset_area=80,
),
Deal(
objectid=2,
deal_amount=1100000,
deal_date=recent - timedelta(days=60),
asset_area=85,
),
]
deal_dates, monthly, _ = parse_deal_dates(deals)
@@ -71,8 +108,18 @@ class TestParseDealDates:
def test_parse_deal_dates_all_valid(self):
"""Test that all valid dates are parsed."""
deals = [
Deal(objectid=1, deal_amount=1000000, deal_date=get_recent_date(months_ago=1), asset_area=80),
Deal(objectid=2, deal_amount=1100000, deal_date=get_recent_date(months_ago=2), asset_area=85),
Deal(
objectid=1,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=1),
asset_area=80,
),
Deal(
objectid=2,
deal_amount=1100000,
deal_date=get_recent_date(months_ago=2),
asset_area=85,
),
]
deal_dates, _, _ = parse_deal_dates(deals)
assert len(deal_dates) == 2
@@ -90,7 +137,12 @@ class TestCalculateMarketActivityScore:
"""Test basic market activity calculation."""
# Create 12 deals spread across last 12 months
deals = [
Deal(objectid=i, deal_amount=1000000, deal_date=get_recent_date(months_ago=i), asset_area=80)
Deal(
objectid=i,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=i),
asset_area=80,
)
for i in range(12)
]
score = calculate_market_activity_score(deals, time_period_months=12)
@@ -106,10 +158,15 @@ class TestCalculateMarketActivityScore:
# Create 120 deals spread across last 10 months = ~12 deals/month (very high)
deals = []
for i in range(120):
month_ago = (i % 10)
month_ago = i % 10
day = (i % 28) + 1
deals.append(
Deal(objectid=i, deal_amount=1000000, deal_date=get_recent_date(months_ago=month_ago, days_ago=day), asset_area=80)
Deal(
objectid=i,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=month_ago, days_ago=day),
asset_area=80,
)
)
score = calculate_market_activity_score(deals, time_period_months=12)
@@ -118,8 +175,18 @@ class TestCalculateMarketActivityScore:
def test_market_activity_low_volume(self):
"""Test activity score with low volume."""
deals = [
Deal(objectid=1, deal_amount=1000000, deal_date=get_recent_date(months_ago=1), asset_area=80),
Deal(objectid=2, deal_amount=1100000, deal_date=get_recent_date(months_ago=6), asset_area=85),
Deal(
objectid=1,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=1),
asset_area=80,
),
Deal(
objectid=2,
deal_amount=1100000,
deal_date=get_recent_date(months_ago=6),
asset_area=85,
),
]
score = calculate_market_activity_score(deals, time_period_months=12)
@@ -135,7 +202,12 @@ class TestCalculateMarketActivityScore:
num_deals = i + 1 # Increasing: 1 deal earliest, 12 deals most recent
for j in range(num_deals):
deals.append(
Deal(objectid=len(deals), deal_amount=1000000, deal_date=get_recent_date(months_ago=months_ago, days_ago=j), asset_area=80)
Deal(
objectid=len(deals),
deal_amount=1000000,
deal_date=get_recent_date(months_ago=months_ago, days_ago=j),
asset_area=80,
)
)
score = calculate_market_activity_score(deals, time_period_months=12)
@@ -150,7 +222,12 @@ class TestCalculateMarketActivityScore:
num_deals = 12 - i # Decreasing: 12 deals earliest, 1 deal most recent
for j in range(num_deals):
deals.append(
Deal(objectid=len(deals), deal_amount=1000000, deal_date=get_recent_date(months_ago=months_ago, days_ago=j), asset_area=80)
Deal(
objectid=len(deals),
deal_amount=1000000,
deal_date=get_recent_date(months_ago=months_ago, days_ago=j),
asset_area=80,
)
)
score = calculate_market_activity_score(deals, time_period_months=12)
@@ -160,7 +237,12 @@ class TestCalculateMarketActivityScore:
"""Test trend detection - stable activity."""
# Same number of deals each month
deals = [
Deal(objectid=i, deal_amount=1000000, deal_date=get_recent_date(months_ago=i), asset_area=80)
Deal(
objectid=i,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=i),
asset_area=80,
)
for i in range(12)
]
score = calculate_market_activity_score(deals, time_period_months=12)
@@ -171,8 +253,18 @@ class TestCalculateMarketActivityScore:
def test_market_activity_insufficient_data_for_trend(self):
"""Test trend with insufficient data."""
deals = [
Deal(objectid=1, deal_amount=1000000, deal_date=get_recent_date(months_ago=1), asset_area=80),
Deal(objectid=2, deal_amount=1100000, deal_date=get_recent_date(months_ago=2), asset_area=85),
Deal(
objectid=1,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=1),
asset_area=80,
),
Deal(
objectid=2,
deal_amount=1100000,
deal_date=get_recent_date(months_ago=2),
asset_area=85,
),
]
score = calculate_market_activity_score(deals, time_period_months=12)
@@ -198,7 +290,12 @@ class TestCalculateMarketActivityScore:
month_ago = i % months
day = (i // months) % 28
deals.append(
Deal(objectid=i, deal_amount=1000000, deal_date=get_recent_date(months_ago=month_ago, days_ago=day), asset_area=80)
Deal(
objectid=i,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=month_ago, days_ago=day),
asset_area=80,
)
)
score = calculate_market_activity_score(deals, time_period_months=12)
@@ -212,9 +309,15 @@ class TestAnalyzeInvestmentPotential:
"""Test basic investment analysis."""
# Create deals with increasing prices
deals = [
Deal(objectid=1, deal_amount=1000000, deal_date="2024-01-01", asset_area=100), # 10000/sqm
Deal(objectid=2, deal_amount=1100000, deal_date="2024-02-01", asset_area=100), # 11000/sqm
Deal(objectid=3, deal_amount=1200000, deal_date="2024-03-01", asset_area=100), # 12000/sqm
Deal(
objectid=1, deal_amount=1000000, deal_date="2024-01-01", asset_area=100
), # 10000/sqm
Deal(
objectid=2, deal_amount=1100000, deal_date="2024-02-01", asset_area=100
), # 11000/sqm
Deal(
objectid=3, deal_amount=1200000, deal_date="2024-03-01", asset_area=100
), # 12000/sqm
]
analysis = analyze_investment_potential(deals)
@@ -253,7 +356,12 @@ class TestAnalyzeInvestmentPotential:
"""Test volatility calculation with low volatility."""
# Prices very similar
deals = [
Deal(objectid=i, deal_amount=1000000 + i*1000, deal_date=f"2024-{i+1:02d}-01", asset_area=100)
Deal(
objectid=i,
deal_amount=1000000 + i * 1000,
deal_date=f"2024-{i + 1:02d}-01",
asset_area=100,
)
for i in range(5)
]
analysis = analyze_investment_potential(deals)
@@ -278,7 +386,12 @@ class TestAnalyzeInvestmentPotential:
def test_investment_analysis_data_quality_excellent(self):
"""Test data quality assessment with excellent data."""
deals = [
Deal(objectid=i, deal_amount=1000000, deal_date=f"2024-{(i%12)+1:02d}-01", asset_area=100)
Deal(
objectid=i,
deal_amount=1000000,
deal_date=f"2024-{(i % 12) + 1:02d}-01",
asset_area=100,
)
for i in range(25)
]
analysis = analyze_investment_potential(deals)
@@ -334,7 +447,7 @@ class TestAnalyzeInvestmentPotential:
def test_investment_analysis_price_trends(self, price_changes, expected_trend):
"""Parametrized test for price trend detection."""
deals = [
Deal(objectid=i, deal_amount=price, deal_date=f"2024-{i+1:02d}-01", asset_area=100)
Deal(objectid=i, deal_amount=price, deal_date=f"2024-{i + 1:02d}-01", asset_area=100)
for i, price in enumerate(price_changes)
]
analysis = analyze_investment_potential(deals)
@@ -348,7 +461,12 @@ class TestGetMarketLiquidity:
def test_market_liquidity_basic(self):
"""Test basic liquidity calculation."""
deals = [
Deal(objectid=i, deal_amount=1000000, deal_date=get_recent_date(months_ago=i), asset_area=80)
Deal(
objectid=i,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=i),
asset_area=80,
)
for i in range(12)
]
liquidity = get_market_liquidity(deals, time_period_months=12)
@@ -367,7 +485,12 @@ class TestGetMarketLiquidity:
month_ago = i % 10
day = (i % 28) + 1
deals.append(
Deal(objectid=i, deal_amount=1000000, deal_date=get_recent_date(months_ago=month_ago, days_ago=day), asset_area=80)
Deal(
objectid=i,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=month_ago, days_ago=day),
asset_area=80,
)
)
liquidity = get_market_liquidity(deals, time_period_months=12)
@@ -377,8 +500,18 @@ class TestGetMarketLiquidity:
def test_market_liquidity_low(self):
"""Test low liquidity."""
deals = [
Deal(objectid=1, deal_amount=1000000, deal_date=get_recent_date(months_ago=1), asset_area=80),
Deal(objectid=2, deal_amount=1100000, deal_date=get_recent_date(months_ago=6), asset_area=85),
Deal(
objectid=1,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=1),
asset_area=80,
),
Deal(
objectid=2,
deal_amount=1100000,
deal_date=get_recent_date(months_ago=6),
asset_area=85,
),
]
liquidity = get_market_liquidity(deals, time_period_months=12)
@@ -389,7 +522,12 @@ class TestGetMarketLiquidity:
"""Test deal velocity calculation."""
# 12 deals spread evenly across 12 months
deals = [
Deal(objectid=i, deal_amount=1000000, deal_date=get_recent_date(months_ago=i), asset_area=80)
Deal(
objectid=i,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=i),
asset_area=80,
)
for i in range(12)
]
liquidity = get_market_liquidity(deals, time_period_months=12)
@@ -420,7 +558,12 @@ class TestGetMarketLiquidity:
month_ago = i % months
day = (i // months) % 28
deals.append(
Deal(objectid=i, deal_amount=1000000, deal_date=get_recent_date(months_ago=month_ago, days_ago=day), asset_area=80)
Deal(
objectid=i,
deal_amount=1000000,
deal_date=get_recent_date(months_ago=month_ago, days_ago=day),
asset_area=80,
)
)
liquidity = get_market_liquidity(deals, time_period_months=12)
+73 -126
View File
@@ -5,21 +5,20 @@ This module tests all Pydantic model validation, serialization,
and computed fields to ensure type safety and correctness.
"""
import pytest
from datetime import datetime
from pydantic import ValidationError
import pytest
from nadlan_mcp.govmap.models import (
CoordinatePoint,
Address,
AutocompleteResult,
AutocompleteResponse,
AutocompleteResult,
CoordinatePoint,
Deal,
DealFilters,
DealStatistics,
MarketActivityScore,
InvestmentAnalysis,
LiquidityMetrics,
DealFilters,
MarketActivityScore,
)
@@ -51,11 +50,7 @@ class TestAddress:
"""Test creating valid address."""
coord = CoordinatePoint(longitude=180000.0, latitude=650000.0)
address = Address(
text="סוקולוב 38 חולון",
id="addr123",
type="address",
score=95.5,
coordinates=coord
text="סוקולוב 38 חולון", id="addr123", type="address", score=95.5, coordinates=coord
)
assert address.text == "סוקולוב 38 חולון"
assert address.score == 95.5
@@ -63,11 +58,7 @@ class TestAddress:
def test_address_without_coordinates(self):
"""Test creating address without coordinates."""
address = Address(
text="סוקולוב 38 חולון",
id="addr123",
type="address"
)
address = Address(text="סוקולוב 38 חולון", id="addr123", type="address")
assert address.coordinates is None
assert address.score == 0 # Default value
@@ -84,7 +75,7 @@ class TestAutocompleteResult:
type="city",
score=100.0,
coordinates=coord,
shape="POINT(180000.0 650000.0)"
shape="POINT(180000.0 650000.0)",
)
assert result.text == "חולון"
assert result.coordinates.longitude == 180000.0
@@ -92,11 +83,7 @@ class TestAutocompleteResult:
def test_result_without_shape(self):
"""Test result without shape data."""
result = AutocompleteResult(
text="חולון",
id="city123",
type="city"
)
result = AutocompleteResult(text="חולון", id="city123", type="city")
assert result.shape is None
assert result.coordinates is None
@@ -108,7 +95,7 @@ class TestAutocompleteResponse:
"""Test creating valid autocomplete response."""
results = [
AutocompleteResult(text="חולון", id="city1", type="city"),
AutocompleteResult(text="חולון סוקולוב", id="street1", type="street")
AutocompleteResult(text="חולון סוקולוב", id="street1", type="street"),
]
response = AutocompleteResponse(resultsCount=2, results=results)
assert response.results_count == 2
@@ -116,10 +103,7 @@ class TestAutocompleteResponse:
def test_response_with_alias(self):
"""Test that camelCase alias works."""
response = AutocompleteResponse.model_validate({
"resultsCount": 5,
"results": []
})
response = AutocompleteResponse.model_validate({"resultsCount": 5, "results": []})
assert response.results_count == 5
def test_empty_response(self):
@@ -143,7 +127,7 @@ class TestDeal:
property_type_description="דירה",
street_name="סוקולוב",
house_number="38",
rooms=3.5
rooms=3.5,
)
assert deal.objectid == 12345
assert deal.deal_amount == 1500000.0
@@ -151,43 +135,31 @@ class TestDeal:
def test_deal_with_aliases(self):
"""Test creating deal using API camelCase field names."""
deal = Deal.model_validate({
"objectid": 12345,
"dealAmount": 1500000.0,
"dealDate": "2024-01-15",
"assetArea": 85.0,
"propertyTypeDescription": "דירה"
})
deal = Deal.model_validate(
{
"objectid": 12345,
"dealAmount": 1500000.0,
"dealDate": "2024-01-15",
"assetArea": 85.0,
"propertyTypeDescription": "דירה",
}
)
assert deal.deal_amount == 1500000.0
assert deal.property_type_description == "דירה"
def test_deal_price_per_sqm_computed(self):
"""Test price_per_sqm computed field."""
deal = Deal(
objectid=12345,
deal_amount=1500000.0,
deal_date="2024-01-15",
asset_area=85.0
)
deal = Deal(objectid=12345, deal_amount=1500000.0, deal_date="2024-01-15", asset_area=85.0)
assert deal.price_per_sqm == round(1500000.0 / 85.0, 2)
def test_deal_price_per_sqm_no_area(self):
"""Test price_per_sqm returns None when area is missing."""
deal = Deal(
objectid=12345,
deal_amount=1500000.0,
deal_date="2024-01-15"
)
deal = Deal(objectid=12345, deal_amount=1500000.0, deal_date="2024-01-15")
assert deal.price_per_sqm is None
def test_deal_price_per_sqm_zero_area(self):
"""Test price_per_sqm returns None when area is zero."""
deal = Deal(
objectid=12345,
deal_amount=1500000.0,
deal_date="2024-01-15",
asset_area=0.0
)
deal = Deal(objectid=12345, deal_amount=1500000.0, deal_date="2024-01-15", asset_area=0.0)
assert deal.price_per_sqm is None
def test_deal_extra_fields_allowed(self):
@@ -197,7 +169,7 @@ class TestDeal:
"dealAmount": 1500000.0,
"dealDate": "2024-01-15",
"extra_field": "extra_value",
"another_field": 123
"another_field": 123,
}
deal = Deal.model_validate(deal_data)
# Extra fields should be stored
@@ -205,12 +177,7 @@ class TestDeal:
def test_deal_serialization(self):
"""Test deal serialization to dict."""
deal = Deal(
objectid=12345,
deal_amount=1500000.0,
deal_date="2024-01-15",
asset_area=85.0
)
deal = Deal(objectid=12345, deal_amount=1500000.0, deal_date="2024-01-15", asset_area=85.0)
deal_dict = deal.model_dump()
assert deal_dict["objectid"] == 12345
assert deal_dict["deal_amount"] == 1500000.0
@@ -218,11 +185,7 @@ class TestDeal:
def test_deal_serialization_exclude_none(self):
"""Test deal serialization excluding None values."""
deal = Deal(
objectid=12345,
deal_amount=1500000.0,
deal_date="2024-01-15"
)
deal = Deal(objectid=12345, deal_amount=1500000.0, deal_date="2024-01-15")
deal_dict = deal.model_dump(exclude_none=True)
assert "asset_area" not in deal_dict
assert "rooms" not in deal_dict
@@ -240,28 +203,11 @@ class TestDealStatistics:
"""Test creating valid deal statistics."""
stats = DealStatistics(
total_deals=100,
price_statistics={
"mean": 1500000.0,
"median": 1400000.0,
"std_dev": 200000.0
},
area_statistics={
"mean": 85.5,
"median": 82.0
},
price_per_sqm_statistics={
"mean": 17500.0,
"median": 17200.0
},
property_type_distribution={
"דירה": 80,
"דירת גן": 15,
"פנטהאוז": 5
},
date_range={
"earliest": "2022-01-01",
"latest": "2024-12-31"
}
price_statistics={"mean": 1500000.0, "median": 1400000.0, "std_dev": 200000.0},
area_statistics={"mean": 85.5, "median": 82.0},
price_per_sqm_statistics={"mean": 17500.0, "median": 17200.0},
property_type_distribution={"דירה": 80, "דירת גן": 15, "פנטהאוז": 5},
date_range={"earliest": "2022-01-01", "latest": "2024-12-31"},
)
assert stats.total_deals == 100
assert stats.price_statistics["mean"] == 1500000.0
@@ -286,7 +232,7 @@ class TestMarketActivityScore:
deals_per_month=10.0,
trend="increasing",
time_period_months=12,
monthly_distribution={"2024-01": 8, "2024-02": 12}
monthly_distribution={"2024-01": 8, "2024-02": 12},
)
assert score.activity_score == 75.5
assert score.trend == "increasing"
@@ -300,7 +246,7 @@ class TestMarketActivityScore:
total_deals=100,
deals_per_month=8.0,
trend="stable",
time_period_months=12
time_period_months=12,
)
with pytest.raises(ValidationError):
@@ -309,7 +255,7 @@ class TestMarketActivityScore:
total_deals=100,
deals_per_month=8.0,
trend="stable",
time_period_months=12
time_period_months=12,
)
@@ -327,7 +273,7 @@ class TestInvestmentAnalysis:
avg_price_per_sqm=17500.0,
price_change_pct=12.5,
total_deals=85,
data_quality="good"
data_quality="good",
)
assert analysis.investment_score == 68.5
assert analysis.price_trend == "increasing"
@@ -345,7 +291,7 @@ class TestInvestmentAnalysis:
avg_price_per_sqm=17000.0,
price_change_pct=5.0,
total_deals=100,
data_quality="excellent"
data_quality="excellent",
)
@@ -360,7 +306,7 @@ class TestLiquidityMetrics:
time_period_months=12,
avg_deals_per_month=12.5,
deal_velocity=12.5,
market_activity_level="high"
market_activity_level="high",
)
assert metrics.liquidity_score == 82.3
assert metrics.market_activity_level == "high"
@@ -381,7 +327,7 @@ class TestDealFilters:
min_area=60.0,
max_area=100.0,
min_floor=1,
max_floor=5
max_floor=5,
)
assert filters.property_type == "דירה"
assert filters.min_rooms == 2.0
@@ -425,45 +371,42 @@ class TestDealFilters:
class TestModelIntegration:
"""Integration tests for models working together."""
def test_deal_to_statistics_workflow(self):
"""Test creating deals and calculating statistics."""
# Import the function to test integration
from nadlan_mcp.govmap.statistics import calculate_deal_statistics
def test_deal_to_statistics_workflow(self):
"""Test creating deals and calculating statistics."""
# Import the function to test integration
from nadlan_mcp.govmap.statistics import calculate_deal_statistics
deals = [
Deal(
objectid=1,
deal_amount=1000000.0,
deal_date="2024-01-01",
asset_area=100.0,
property_type_description="דירה"
),
Deal(
objectid=2,
deal_amount=2000000.0,
deal_date="2024-01-02",
asset_area=100.0,
property_type_description="דירה"
),
]
deals = [
Deal(
objectid=1,
deal_amount=1000000.0,
deal_date="2024-01-01",
asset_area=100.0,
property_type_description="דירה",
),
Deal(
objectid=2,
deal_amount=2000000.0,
deal_date="2024-01-02",
asset_area=100.0,
property_type_description="דירה",
),
]
stats = calculate_deal_statistics(deals)
stats = calculate_deal_statistics(deals)
assert isinstance(stats, DealStatistics)
assert stats.total_deals == 2
assert stats.price_statistics["mean"] == 1500000.0
assert stats.price_per_sqm_statistics["mean"] == 15000.0
assert stats.property_type_distribution["דירה"] == 2
assert isinstance(stats, DealStatistics)
assert stats.total_deals == 2
assert stats.price_statistics["mean"] == 1500000.0
assert stats.price_per_sqm_statistics["mean"] == 15000.0
assert stats.property_type_distribution["דירה"] == 2
def test_autocomplete_to_deals_workflow(self):
"""Test autocomplete response leading to deal search."""
# Simulate autocomplete response
coord = CoordinatePoint(longitude=180000.0, latitude=650000.0)
result = AutocompleteResult(
text="סוקולוב 38 חולון",
id="addr123",
type="address",
coordinates=coord
text="סוקולוב 38 חולון", id="addr123", type="address", coordinates=coord
)
response = AutocompleteResponse(resultsCount=1, results=[result])
@@ -478,14 +421,18 @@ class TestModelIntegration:
min_rooms=3.0,
max_rooms=4.0,
min_price=1000000.0,
max_price=2000000.0
max_price=2000000.0,
)
# Create test deals
deals = [
Deal(objectid=1, deal_amount=1200000.0, deal_date="2024-01-01", rooms=3.0),
Deal(objectid=2, deal_amount=2500000.0, deal_date="2024-01-02", rooms=4.0), # Price too high
Deal(objectid=3, deal_amount=1500000.0, deal_date="2024-01-03", rooms=2.0), # Too few rooms
Deal(
objectid=2, deal_amount=2500000.0, deal_date="2024-01-02", rooms=4.0
), # Price too high
Deal(
objectid=3, deal_amount=1500000.0, deal_date="2024-01-03", rooms=2.0
), # Too few rooms
]
# Manually check which deals would pass
+60 -13
View File
@@ -4,10 +4,12 @@ Tests for nadlan_mcp.govmap.statistics module.
Comprehensive tests for statistical calculation functions.
"""
import pytest
from datetime import date
from nadlan_mcp.govmap.statistics import calculate_deal_statistics, calculate_std_dev
import pytest
from nadlan_mcp.govmap.models import Deal, DealStatistics
from nadlan_mcp.govmap.statistics import calculate_deal_statistics, calculate_std_dev
class TestCalculateDealStatistics:
@@ -135,8 +137,12 @@ class TestCalculateDealStatistics:
"""Test statistics when some deals have zero prices."""
deals = [
Deal(objectid=1, deal_amount=1000000.0, deal_date="2024-01-01", asset_area=80.0),
Deal(objectid=2, deal_amount=0.0, deal_date="2024-01-02", asset_area=100.0), # Zero price excluded
Deal(objectid=3, deal_amount=-1.0, deal_date="2024-01-03", asset_area=120.0), # Negative excluded
Deal(
objectid=2, deal_amount=0.0, deal_date="2024-01-02", asset_area=100.0
), # Zero price excluded
Deal(
objectid=3, deal_amount=-1.0, deal_date="2024-01-03", asset_area=120.0
), # Negative excluded
]
stats = calculate_deal_statistics(deals)
@@ -161,8 +167,18 @@ class TestCalculateDealStatistics:
def test_deals_with_missing_property_types(self):
"""Test statistics when some deals have missing property types."""
deals = [
Deal(objectid=1, deal_amount=1000000.0, deal_date="2024-01-01", property_type_description="דירה"),
Deal(objectid=2, deal_amount=1500000.0, deal_date="2024-01-02", property_type_description=None),
Deal(
objectid=1,
deal_amount=1000000.0,
deal_date="2024-01-01",
property_type_description="דירה",
),
Deal(
objectid=2,
deal_amount=1500000.0,
deal_date="2024-01-02",
property_type_description=None,
),
Deal(objectid=3, deal_amount=2000000.0, deal_date="2024-01-03"), # No property_type
]
stats = calculate_deal_statistics(deals)
@@ -196,7 +212,13 @@ class TestCalculateDealStatistics:
def test_single_deal(self):
"""Test statistics with single deal."""
deals = [
Deal(objectid=1, deal_amount=1000000.0, deal_date="2024-01-01", asset_area=80.0, property_type_description="דירה")
Deal(
objectid=1,
deal_amount=1000000.0,
deal_date="2024-01-01",
asset_area=80.0,
property_type_description="דירה",
)
]
stats = calculate_deal_statistics(deals)
@@ -238,8 +260,18 @@ class TestCalculateDealStatistics:
def test_date_handling_with_iso_strings(self):
"""Test date range calculation with ISO format strings."""
deals = [
Deal(objectid=1, deal_amount=1000000.0, deal_date="2024-01-15T00:00:00.000Z", asset_area=80.0),
Deal(objectid=2, deal_amount=1500000.0, deal_date="2024-02-01T12:30:45.123Z", asset_area=100.0),
Deal(
objectid=1,
deal_amount=1000000.0,
deal_date="2024-01-15T00:00:00.000Z",
asset_area=80.0,
),
Deal(
objectid=2,
deal_amount=1500000.0,
deal_date="2024-02-01T12:30:45.123Z",
asset_area=100.0,
),
]
stats = calculate_deal_statistics(deals)
@@ -250,9 +282,24 @@ class TestCalculateDealStatistics:
def test_multiple_same_property_types(self):
"""Test property type distribution with duplicates."""
deals = [
Deal(objectid=1, deal_amount=1000000.0, deal_date="2024-01-01", property_type_description="דירה"),
Deal(objectid=2, deal_amount=1500000.0, deal_date="2024-01-02", property_type_description="דירה"),
Deal(objectid=3, deal_amount=2000000.0, deal_date="2024-01-03", property_type_description="דירה"),
Deal(
objectid=1,
deal_amount=1000000.0,
deal_date="2024-01-01",
property_type_description="דירה",
),
Deal(
objectid=2,
deal_amount=1500000.0,
deal_date="2024-01-02",
property_type_description="דירה",
),
Deal(
objectid=3,
deal_amount=2000000.0,
deal_date="2024-01-03",
property_type_description="דירה",
),
]
stats = calculate_deal_statistics(deals)
@@ -289,7 +336,7 @@ class TestCalculateDealStatistics:
Deal(
objectid=i,
deal_amount=1000000.0 if has_price else 0.0, # Use 0.0 instead of None
deal_date=f"2024-01-{i+1:02d}",
deal_date=f"2024-01-{i + 1:02d}",
asset_area=80.0 if has_area else None,
)
)
+1 -2
View File
@@ -4,11 +4,10 @@ Unit tests for utils module.
Tests helper utilities including distance calculation, address matching, and floor parsing.
"""
import pytest
from nadlan_mcp.govmap.utils import (
calculate_distance,
is_same_building,
extract_floor_number,
is_same_building,
)
+2 -1
View File
@@ -5,11 +5,12 @@ Tests input validation functions for addresses, coordinates, integers, and deal
"""
import pytest
from nadlan_mcp.govmap.validators import (
validate_address,
validate_coordinates,
validate_positive_int,
validate_deal_type,
validate_positive_int,
)