Normalize MCP response structures across all tools

Implement changes from MCP_NORMALIZATION_FIX.md to provide consistent
response structure across all MCP tools. This fixes the bot integration
issue where different tools returned data in different structures.

Changes:
- get_valuation_comparables:
  - Rename "comparables" → "deals"
  - Move "total_comparables" → "market_statistics.deal_breakdown.total_deals"
  - Add "search_parameters" section with "filters_applied"
  - Move "statistics" → "market_statistics"

- find_recent_deals_for_address:
  - Rename "price_stats.average_price" → "price_statistics.mean"
  - Rename "price_stats.median_price" → "price_statistics.median"
  - Rename "area_stats" → "area_statistics"
  - Rename "price_per_sqm_stats" → "price_per_sqm_statistics"

- get_deal_statistics:
  - Add "search_parameters" section
  - Move "statistics" → "market_statistics"
  - Add "market_statistics.deal_breakdown.total_deals"

- analyze_market_trends:
  - Add "market_statistics.deal_breakdown.total_deals"
  - Keep existing tool-specific fields (yearly_trends, etc.)

- get_market_activity_metrics:
  - Add "market_statistics.deal_breakdown.total_deals"
  - Keep existing tool-specific metrics

All tools now follow standard structure:
{
  "search_parameters" or "analysis_parameters": {...},
  "market_statistics": {
    "deal_breakdown": {"total_deals": N},
    "price_statistics": {"mean": ..., "median": ...},
    "area_statistics": {...},
    "price_per_sqm_statistics": {...}
  },
  "deals": [...]
}

Updated tests to match new normalized structure.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Nitzan P
2025-11-23 09:49:52 +02:00
parent c444e88a10
commit e791d3c1ed
4 changed files with 476 additions and 76 deletions
+23 -16
View File
@@ -75,11 +75,12 @@ class TestMCPToolsE2E:
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
# Check response structure (normalized in MCP_NORMALIZATION_FIX)
assert "market_statistics" in data
assert "deal_breakdown" in data["market_statistics"]
assert "total_deals" in data["market_statistics"]["deal_breakdown"]
assert isinstance(data["market_statistics"]["deal_breakdown"]["total_deals"], int)
assert data["market_statistics"]["deal_breakdown"]["total_deals"] >= 0
def test_get_valuation_comparables(self):
"""Test getting valuation comparables."""
@@ -88,13 +89,17 @@ class TestMCPToolsE2E:
)
data = json.loads(result)
assert "total_comparables" in data
assert isinstance(data["total_comparables"], int)
assert data["total_comparables"] >= 0
# Normalized structure: total_comparables -> market_statistics.deal_breakdown.total_deals
assert "market_statistics" in data
assert "deal_breakdown" in data["market_statistics"]
assert "total_deals" in data["market_statistics"]["deal_breakdown"]
assert isinstance(data["market_statistics"]["deal_breakdown"]["total_deals"], int)
assert data["market_statistics"]["deal_breakdown"]["total_deals"] >= 0
if data["total_comparables"] > 0:
assert "comparables" in data
comp = data["comparables"][0]
# Normalized structure: comparables -> deals
if data["market_statistics"]["deal_breakdown"]["total_deals"] > 0:
assert "deals" in data
comp = data["deals"][0]
assert "deal_amount" in comp
# rooms field is optional and excluded when None
# Just verify the comparable has basic required fields
@@ -105,11 +110,13 @@ class TestMCPToolsE2E:
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
# Check response structure (normalized: statistics -> market_statistics)
assert "market_statistics" in data
# Check for total_deals in normalized location
if "deal_breakdown" in data["market_statistics"]:
assert "total_deals" in data["market_statistics"]["deal_breakdown"]
assert isinstance(data["market_statistics"]["deal_breakdown"]["total_deals"], int)
assert data["market_statistics"]["deal_breakdown"]["total_deals"] >= 0
def test_get_market_activity_metrics(self):
"""Test market activity metrics."""
+15 -8
View File
@@ -364,10 +364,13 @@ class TestGetValuationComparables:
)
parsed = json.loads(result)
assert "filters_applied" in parsed
assert "statistics" in parsed
assert "comparables" in parsed
assert parsed["filters_applied"]["property_type"] == "דירה"
# Normalized structure: filters_applied is now in search_parameters
assert "search_parameters" in parsed
assert "filters_applied" in parsed["search_parameters"]
assert parsed["search_parameters"]["filters_applied"]["property_type"] == "דירה"
# Normalized structure: comparables -> deals
assert "deals" in parsed
assert "market_statistics" in parsed
@patch("nadlan_mcp.fastmcp_server.client")
def test_comparables_strips_bloat(self, mock_client):
@@ -392,7 +395,8 @@ class TestGetValuationComparables:
result = fastmcp_server.get_valuation_comparables("test address")
parsed = json.loads(result)
comparable = parsed["comparables"][0]
# Normalized structure: comparables -> deals
comparable = parsed["deals"][0]
assert "shape" not in comparable
assert "sourceorder" not in comparable
# source_polygon_id is kept when added by processing
@@ -421,9 +425,12 @@ class TestGetDealStatistics:
result = fastmcp_server.get_deal_statistics("test address")
parsed = json.loads(result)
assert "address" in parsed
assert "statistics" in parsed
assert parsed["statistics"]["total_deals"] == 2 # Field name is total_deals in model
# Normalized structure: address is now in search_parameters, statistics -> market_statistics
assert "search_parameters" in parsed
assert "address" in parsed["search_parameters"]
assert "market_statistics" in parsed
assert "deal_breakdown" in parsed["market_statistics"]
assert parsed["market_statistics"]["deal_breakdown"]["total_deals"] == 2
class TestGetMarketActivityMetrics: