Complete Phase 4.1 test suite updates - all 174 tests passing
Fixed all remaining test failures after Pydantic v2 migration: Core fixes: - Date handling: Convert date objects to ISO strings across 4 files - Model serialization: Use model_dump(mode='json') for JSON compatibility - Optional fields: Made time_period_months Optional[int] in models - Dict access: Replace .get() with getattr() for dynamic attributes Test updates: - Updated 50+ test fixtures from dicts to Deal models - Fixed date-based tests to use recent dates for time filtering - Added missing imports (CoordinatePoint, MarketActivityScore, DealStatistics) - Updated assertions from dict keys to model attributes (snake_case) Files modified: - nadlan_mcp/govmap/market_analysis.py - nadlan_mcp/govmap/statistics.py - nadlan_mcp/govmap/models.py - nadlan_mcp/fastmcp_server.py - tests/test_govmap_client.py - tests/test_fastmcp_tools.py - .cursor/plans/TEST-UPDATE-STATUS.md (comprehensive documentation) Result: 174/174 tests passing (100%) ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,8 @@ def strip_bloat_fields(deals: List[Deal]) -> List[Dict[str, Any]]:
|
||||
result = []
|
||||
for deal in deals:
|
||||
# Convert Deal model to dict, excluding None values for cleaner output
|
||||
deal_dict = deal.model_dump(exclude_none=True)
|
||||
# Use mode='json' to serialize dates as ISO strings
|
||||
deal_dict = deal.model_dump(mode='json', exclude_none=True)
|
||||
|
||||
# Remove bloat fields
|
||||
filtered_dict = {k: v for k, v in deal_dict.items() if k not in bloat_fields}
|
||||
@@ -342,10 +343,12 @@ def analyze_market_trends(address: str, years_back: int = 3, radius_meters: int
|
||||
|
||||
# Simplified processing - extract only essential data
|
||||
for deal in deals:
|
||||
date_str = deal.deal_date
|
||||
if not date_str:
|
||||
if not deal.deal_date:
|
||||
continue
|
||||
|
||||
# Convert date to string for parsing
|
||||
from datetime import date as date_type
|
||||
date_str = deal.deal_date.isoformat() if isinstance(deal.deal_date, date_type) else str(deal.deal_date)
|
||||
year = date_str[:4]
|
||||
price = deal.deal_amount
|
||||
area = deal.asset_area
|
||||
@@ -450,7 +453,7 @@ def analyze_market_trends(address: str, years_back: int = 3, radius_meters: int
|
||||
"key_insights": {
|
||||
"most_active_year": max(yearly_trends.keys(), key=lambda y: yearly_trends[y]['deal_count']) if yearly_trends else None,
|
||||
"highest_avg_price_year": max(yearly_trends.keys(), key=lambda y: yearly_trends[y]['avg_price_per_sqm']) if yearly_trends else None,
|
||||
"deal_source_summary": f"Building: {len([d for d in deals if d.get('deal_source') == 'same_building'])}, Street: {len([d for d in deals if d.get('deal_source') == 'street'])}, Neighborhood: {len([d for d in deals if d.get('deal_source') == 'neighborhood'])}"
|
||||
"deal_source_summary": f"Building: {len([d for d in deals if getattr(d, 'deal_source', None) == 'same_building'])}, Street: {len([d for d in deals if getattr(d, 'deal_source', None) == 'street'])}, Neighborhood: {len([d for d in deals if getattr(d, 'deal_source', None) == 'neighborhood'])}"
|
||||
}
|
||||
}, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Focused on providing data metrics; the LLM interprets them for investment advice
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from .models import Deal, MarketActivityScore, InvestmentAnalysis, LiquidityMetrics
|
||||
@@ -68,11 +68,13 @@ def parse_deal_dates(
|
||||
deal_dates = []
|
||||
|
||||
for deal in deals:
|
||||
date_str = deal.deal_date
|
||||
if not date_str:
|
||||
if not deal.deal_date:
|
||||
continue
|
||||
|
||||
try:
|
||||
# Convert date to string for comparison and parsing
|
||||
date_str = deal.deal_date.isoformat() if isinstance(deal.deal_date, date) else str(deal.deal_date)
|
||||
|
||||
# Filter by time period if specified
|
||||
if cutoff_date is not None and date_str < cutoff_date_str:
|
||||
continue
|
||||
@@ -100,7 +102,7 @@ def parse_deal_dates(
|
||||
|
||||
|
||||
def calculate_market_activity_score(
|
||||
deals: List[Deal], time_period_months: int = 12
|
||||
deals: List[Deal], time_period_months: Optional[int] = 12
|
||||
) -> MarketActivityScore:
|
||||
"""
|
||||
Calculate market activity and liquidity metrics.
|
||||
@@ -209,10 +211,12 @@ def analyze_investment_potential(deals: List[Deal]) -> InvestmentAnalysis:
|
||||
price_data = []
|
||||
for deal in deals:
|
||||
price_per_sqm = deal.price_per_sqm # Use computed field from Deal model
|
||||
date_str = deal.deal_date
|
||||
|
||||
if price_per_sqm and price_per_sqm > 0 and date_str:
|
||||
if price_per_sqm and price_per_sqm > 0 and deal.deal_date:
|
||||
try:
|
||||
# Convert date to string for parsing
|
||||
date_str = deal.deal_date.isoformat() if isinstance(deal.deal_date, date) else str(deal.deal_date)
|
||||
|
||||
# Parse date for sorting
|
||||
year = int(date_str[:4])
|
||||
month = int(date_str[5:7])
|
||||
@@ -319,7 +323,7 @@ def analyze_investment_potential(deals: List[Deal]) -> InvestmentAnalysis:
|
||||
|
||||
|
||||
def get_market_liquidity(
|
||||
deals: List[Deal], time_period_months: int = 12
|
||||
deals: List[Deal], time_period_months: Optional[int] = 12
|
||||
) -> LiquidityMetrics:
|
||||
"""
|
||||
Get detailed market liquidity and turnover metrics.
|
||||
|
||||
@@ -228,7 +228,7 @@ class MarketActivityScore(BaseModel):
|
||||
total_deals: int = Field(..., description="Total deals in period")
|
||||
deals_per_month: float = Field(..., description="Average deals per month")
|
||||
trend: str = Field(..., description="Market trend (increasing, stable, decreasing)")
|
||||
time_period_months: int = Field(..., description="Analysis period in months")
|
||||
time_period_months: Optional[int] = Field(None, description="Analysis period in months (None = all data)")
|
||||
monthly_distribution: Dict[str, int] = Field(
|
||||
default_factory=dict,
|
||||
description="Deals per month (YYYY-MM: count)"
|
||||
@@ -275,7 +275,7 @@ class LiquidityMetrics(BaseModel):
|
||||
"""
|
||||
liquidity_score: float = Field(..., description="Overall liquidity score (0-100)", ge=0, le=100)
|
||||
total_deals: int = Field(..., description="Total deals in period")
|
||||
time_period_months: int = Field(..., description="Analysis period in months")
|
||||
time_period_months: Optional[int] = Field(None, description="Analysis period in months (None = all data)")
|
||||
avg_deals_per_month: float = Field(..., description="Average deals per month")
|
||||
deal_velocity: float = Field(..., description="Deal velocity (deals per month)")
|
||||
market_activity_level: str = Field(..., description="Activity level (very_high, high, moderate, low, very_low)")
|
||||
|
||||
@@ -121,16 +121,23 @@ def calculate_deal_statistics(deals: List[Deal]) -> DealStatistics:
|
||||
date_range_dict = None
|
||||
if deal_dates:
|
||||
try:
|
||||
# Parse ISO date strings to get earliest and latest
|
||||
# Convert dates to ISO strings for consistent formatting
|
||||
from datetime import date as date_type
|
||||
parsed_dates = []
|
||||
for date_str in deal_dates:
|
||||
for d in deal_dates:
|
||||
try:
|
||||
# Handle ISO format with timezone (e.g., "2025-01-01T00:00:00.000Z")
|
||||
if 'T' in date_str:
|
||||
date_str = date_str.split('T')[0]
|
||||
parsed_dates.append(date_str)
|
||||
# Handle date objects (from Pydantic models)
|
||||
if isinstance(d, date_type):
|
||||
parsed_dates.append(d.isoformat())
|
||||
else:
|
||||
# Handle string dates
|
||||
date_str = str(d)
|
||||
# Handle ISO format with timezone (e.g., "2025-01-01T00:00:00.000Z")
|
||||
if 'T' in date_str:
|
||||
date_str = date_str.split('T')[0]
|
||||
parsed_dates.append(date_str)
|
||||
except (ValueError, TypeError):
|
||||
logger.warning(f"Invalid date format: {date_str}")
|
||||
logger.warning(f"Invalid date format: {d}")
|
||||
continue
|
||||
|
||||
if parsed_dates:
|
||||
@@ -140,7 +147,7 @@ def calculate_deal_statistics(deals: List[Deal]) -> DealStatistics:
|
||||
"latest": sorted_dates[-1],
|
||||
}
|
||||
except (ValueError, TypeError):
|
||||
logger.warning("Invalid date format: {date_range_dict}")
|
||||
logger.warning("Invalid date format in date range calculation")
|
||||
pass
|
||||
|
||||
return DealStatistics(
|
||||
|
||||
Reference in New Issue
Block a user