Merge pull request #1 from nitzpo/comprehensive-plan-phase-1
Comprehensive-plan-phase-1
This commit is contained in:
@@ -0,0 +1,482 @@
|
|||||||
|
<!-- f18f82e2-05d6-4de1-bb9b-9ca39dca90fc 9249b19a-20d4-4473-be7a-f0b5bf9a0301 -->
|
||||||
|
# Nadlan MCP Comprehensive Improvement Plan
|
||||||
|
|
||||||
|
## Phase 1: Critical Code Quality & Reliability Fixes
|
||||||
|
|
||||||
|
### 1.1 Error Handling & Resilience
|
||||||
|
|
||||||
|
**Files: `nadlan_mcp/govmap.py`, `nadlan_mcp/fastmcp_server.py`**
|
||||||
|
|
||||||
|
- Add retry logic with exponential backoff for API calls (use `tenacity` library)
|
||||||
|
- Implement rate limiting protection (track request counts, add delays)
|
||||||
|
- Standardize error handling: all GovmapClient methods should raise exceptions (not return empty lists)
|
||||||
|
- Add proper input validation/sanitization for all functions
|
||||||
|
- Add timeout configuration for HTTP requests
|
||||||
|
|
||||||
|
**Issues identified:**
|
||||||
|
|
||||||
|
- Line 111-140 in `govmap.py`: `get_deals_by_radius` catches JSONDecodeError and returns `[]` instead of raising
|
||||||
|
- Line 180-185 in `govmap.py`: `get_street_deals` catches JSONDecodeError and returns `[]` instead of raising
|
||||||
|
- Similar pattern in `get_neighborhood_deals`
|
||||||
|
- No retry logic anywhere
|
||||||
|
- No rate limiting protection
|
||||||
|
|
||||||
|
### 1.2 Configuration Management
|
||||||
|
|
||||||
|
**New file: `nadlan_mcp/config.py`**
|
||||||
|
|
||||||
|
- Create configuration class with all settings (API base URL, timeouts, retry counts, rate limits)
|
||||||
|
- Support environment variables for configuration
|
||||||
|
- Add validation for configuration values
|
||||||
|
- Document all configuration options
|
||||||
|
|
||||||
|
**Currently hard-coded values:**
|
||||||
|
|
||||||
|
- API base URL in `govmap.py:19`
|
||||||
|
- Default timeouts, radii, limits scattered throughout
|
||||||
|
- No environment variable support beyond basic setup
|
||||||
|
|
||||||
|
### 1.3 Documentation Alignment
|
||||||
|
|
||||||
|
**Files: `USECASES.md`, `README.md`**
|
||||||
|
|
||||||
|
- Update USECASES.md to clearly mark amenity scoring as "Future Feature" with roadmap
|
||||||
|
- Add architecture diagram showing MCP → Govmap API flow
|
||||||
|
- Create ARCHITECTURE.md documenting design decisions
|
||||||
|
- Update README.md with current accurate feature list
|
||||||
|
- Add API limitations and rate limit documentation
|
||||||
|
|
||||||
|
**Misalignments found:**
|
||||||
|
|
||||||
|
- USECASES.md lines 23-28, 51-56, 66-69 describe amenity features that don't exist
|
||||||
|
- README mentions features not fully implemented
|
||||||
|
|
||||||
|
## Phase 2: Missing Core Functionality
|
||||||
|
|
||||||
|
### 2.1 Property Valuation Data Provision
|
||||||
|
|
||||||
|
**Updates to `nadlan_mcp/govmap.py`, Add tools to `fastmcp_server.py`**
|
||||||
|
|
||||||
|
**Purpose:** Provide comprehensive data for LLM to perform its own valuation analysis
|
||||||
|
|
||||||
|
**New Functions:**
|
||||||
|
|
||||||
|
- `get_comparable_properties(address, filters)` - find similar properties with flexible filtering
|
||||||
|
- `calculate_simple_statistics(deals)` - basic math helper for price/sqm calculations (optional utility)
|
||||||
|
|
||||||
|
**Tools for LLM:**
|
||||||
|
|
||||||
|
- `get_valuation_comparables` - return detailed comparable deals filtered by criteria
|
||||||
|
- Filter by: property type, room count, area range, floor range, time period
|
||||||
|
- Include: full deal history, neighborhood data, price trends
|
||||||
|
- Let LLM analyze and estimate value
|
||||||
|
- `get_deal_statistics` - simple statistical aggregations (mean, median, percentiles)
|
||||||
|
- For when LLM needs quick calculations on large datasets
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
|
||||||
|
- Enhanced filtering on existing deal data
|
||||||
|
- Statistical helper functions (no ML, no predictions)
|
||||||
|
- Rich contextual data for LLM decision-making
|
||||||
|
- LLM does the valuation, MCP provides the data
|
||||||
|
|
||||||
|
### 2.2 Market Activity & Investment Analysis
|
||||||
|
|
||||||
|
**New file: `nadlan_mcp/market_analysis.py`, Add tools to `fastmcp_server.py`**
|
||||||
|
|
||||||
|
**New Functions:**
|
||||||
|
|
||||||
|
- `calculate_market_activity_score(address, time_period)` - volume and velocity metrics
|
||||||
|
- `analyze_investment_potential(address, criteria)` - ROI, appreciation, risk metrics
|
||||||
|
- `get_market_liquidity(neighborhood)` - time-to-sell, supply/demand
|
||||||
|
|
||||||
|
**Tools for LLM:**
|
||||||
|
|
||||||
|
- `get_market_activity_metrics` - deal volume, velocity, inventory levels
|
||||||
|
- `analyze_investment_opportunity` - comprehensive investment analysis
|
||||||
|
- `get_neighborhood_market_health` - market health indicators
|
||||||
|
|
||||||
|
**Metrics to include:**
|
||||||
|
|
||||||
|
- Deal volume trends (deals per month)
|
||||||
|
- Average time on market (if available)
|
||||||
|
- Price appreciation rates
|
||||||
|
- Supply/demand indicators (active listings vs deals)
|
||||||
|
- Neighborhood investment score (0-100)
|
||||||
|
|
||||||
|
### 2.3 Enhanced Deal Filtering & Search
|
||||||
|
|
||||||
|
**Updates to `nadlan_mcp/govmap.py`**
|
||||||
|
|
||||||
|
**New Functions:**
|
||||||
|
|
||||||
|
- `filter_deals_by_criteria(deals, criteria)` - filter by rooms, area, price range, property type
|
||||||
|
- `search_deals_with_filters(address, filters, years_back)` - combined search with filters
|
||||||
|
- `get_deals_by_property_type(address, property_type, years_back)` - specialized property type search
|
||||||
|
|
||||||
|
**Add to existing functions:**
|
||||||
|
|
||||||
|
- Property type filtering (apartment, house, penthouse, etc.)
|
||||||
|
- Room count filtering (2-room, 3-room, etc.)
|
||||||
|
- Price range filtering
|
||||||
|
- Area range filtering
|
||||||
|
- Floor range filtering
|
||||||
|
|
||||||
|
## Phase 3: Architecture & Data Model Improvements
|
||||||
|
|
||||||
|
### 3.1 Create Structured Data Models
|
||||||
|
|
||||||
|
**New file: `nadlan_mcp/models.py`**
|
||||||
|
|
||||||
|
Create Pydantic models for:
|
||||||
|
|
||||||
|
- `Deal` - standardized deal object
|
||||||
|
- `Address` - address with coordinates and metadata
|
||||||
|
- `MarketAnalysis` - market trend analysis results
|
||||||
|
- `PropertyValuation` - valuation estimates
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
|
||||||
|
- Type safety and validation
|
||||||
|
- Consistent JSON serialization
|
||||||
|
- Better IDE support
|
||||||
|
- Easier testing
|
||||||
|
|
||||||
|
### 3.2 Separate Concerns
|
||||||
|
|
||||||
|
**Refactor existing code:**
|
||||||
|
|
||||||
|
- Move analysis logic from `fastmcp_server.py` into dedicated modules
|
||||||
|
- Create `nadlan_mcp/api_client.py` for pure API interactions
|
||||||
|
- Create `nadlan_mcp/analyzers/` package for analysis functions
|
||||||
|
- Keep `fastmcp_server.py` thin - just MCP tool definitions
|
||||||
|
|
||||||
|
**Current issues:**
|
||||||
|
|
||||||
|
- `fastmcp_server.py` has 537 lines mixing API calls, analysis, and MCP definitions
|
||||||
|
- Business logic scattered across client and server
|
||||||
|
- Hard to test and maintain
|
||||||
|
|
||||||
|
### 3.3 Improve Tool Design for LLM Consumption
|
||||||
|
|
||||||
|
**Update all tools in `fastmcp_server.py`**
|
||||||
|
|
||||||
|
Each tool should offer:
|
||||||
|
|
||||||
|
- **Structured mode** (default): Return full structured data for LLM processing
|
||||||
|
- **Summarized mode** (optional): Return analyzed/summarized data for simple queries
|
||||||
|
- Clear parameter documentation with examples
|
||||||
|
- Consistent error responses
|
||||||
|
|
||||||
|
**Parameter pattern:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
@mcp.tool()
|
||||||
|
def find_recent_deals_for_address(
|
||||||
|
address: str,
|
||||||
|
years_back: int = 2,
|
||||||
|
summarized_response: bool = False # Default: structured/detailed
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
summarized_response:
|
||||||
|
- False (default): Full deal data with all fields for LLM processing
|
||||||
|
- True: Statistical summary with key insights only
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 4: Testing & Quality Assurance
|
||||||
|
|
||||||
|
### 4.1 Expand Test Coverage
|
||||||
|
|
||||||
|
**Updates to `tests/test_govmap_client.py`, new test files**
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
- Integration tests for complete workflows (mark with `@pytest.mark.integration`)
|
||||||
|
- Edge case tests (empty results, malformed data, network errors)
|
||||||
|
- Parametrized tests for different address formats
|
||||||
|
- Tests for new functionality (valuation data, market analysis)
|
||||||
|
|
||||||
|
**Current coverage gaps:**
|
||||||
|
|
||||||
|
- No tests for `analyze_market_trends`
|
||||||
|
- No tests for `compare_addresses`
|
||||||
|
- No tests for error recovery
|
||||||
|
- No tests for `_is_same_building` logic
|
||||||
|
|
||||||
|
### 4.2 Add Validation Tests
|
||||||
|
|
||||||
|
**New file: `tests/test_validation.py`**
|
||||||
|
|
||||||
|
- Test input validation (invalid addresses, negative numbers, etc.)
|
||||||
|
- Test configuration validation
|
||||||
|
- Test data model validation (when Pydantic models added)
|
||||||
|
|
||||||
|
### 4.3 Mock External APIs
|
||||||
|
|
||||||
|
**Updates to `tests/conftest.py`**
|
||||||
|
|
||||||
|
- Create comprehensive fixtures for API responses
|
||||||
|
- Add vcr.py for recording/replaying API calls in tests
|
||||||
|
|
||||||
|
## Phase 5: Documentation & Developer Experience
|
||||||
|
|
||||||
|
### 5.1 Create Comprehensive Documentation
|
||||||
|
|
||||||
|
**New files:**
|
||||||
|
|
||||||
|
- `ARCHITECTURE.md` - system design, data flow, component interactions
|
||||||
|
- `DEPLOYMENT.md` - deployment guide for production use
|
||||||
|
- `CONTRIBUTING.md` - contribution guidelines
|
||||||
|
- `API_REFERENCE.md` - detailed API documentation with examples
|
||||||
|
- `CLAUDE.md` - instructions for AI coding agents implementing future tasks
|
||||||
|
- Task breakdown format
|
||||||
|
- Code standards and patterns
|
||||||
|
- Testing requirements
|
||||||
|
- Common pitfalls and solutions
|
||||||
|
|
||||||
|
### 5.2 Improve Code Documentation
|
||||||
|
|
||||||
|
**All Python files:**
|
||||||
|
|
||||||
|
- Add module-level docstrings explaining purpose
|
||||||
|
- Add comprehensive function docstrings with examples
|
||||||
|
- Add type hints to all functions (including returns)
|
||||||
|
- Add inline comments for complex logic
|
||||||
|
|
||||||
|
### 5.3 Add Usage Examples
|
||||||
|
|
||||||
|
**New directory: `examples/`**
|
||||||
|
|
||||||
|
Create example scripts:
|
||||||
|
|
||||||
|
- `examples/basic_search.py` - simple address search
|
||||||
|
- `examples/market_analysis.py` - comprehensive market analysis
|
||||||
|
- `examples/investment_analysis.py` - investment opportunity analysis
|
||||||
|
- `examples/llm_integration.py` - example LLM agent using the MCP
|
||||||
|
|
||||||
|
### 5.4 Update USECASES.md
|
||||||
|
|
||||||
|
**File: `USECASES.md`**
|
||||||
|
|
||||||
|
- Add status indicators for each feature (✅ Implemented, 🚧 In Progress, 📋 Planned)
|
||||||
|
- Add implementation priority levels
|
||||||
|
- Add expected completion timeline
|
||||||
|
- Add links to relevant documentation
|
||||||
|
|
||||||
|
## Phase 6: Cleanup & Optimization
|
||||||
|
|
||||||
|
### 6.1 Remove Redundant Code
|
||||||
|
|
||||||
|
**Files to clean:**
|
||||||
|
|
||||||
|
- Delete or integrate `mcp_server_concept.py` (appears to be duplicate/old code)
|
||||||
|
- Remove unused imports
|
||||||
|
- Remove dead code
|
||||||
|
- Consolidate duplicate logic
|
||||||
|
|
||||||
|
### 6.2 Code Style & Linting
|
||||||
|
|
||||||
|
**New file: `.pre-commit-config.yaml`**
|
||||||
|
|
||||||
|
- Setup pre-commit hooks (black, isort, flake8, mypy)
|
||||||
|
- Format all code consistently
|
||||||
|
- Fix all linter warnings
|
||||||
|
- Add type checking with mypy
|
||||||
|
|
||||||
|
### 6.3 Dependency Management
|
||||||
|
|
||||||
|
**Updates to `requirements.txt`**
|
||||||
|
|
||||||
|
Current dependencies are minimal. Add:
|
||||||
|
|
||||||
|
- `tenacity` - for retry logic
|
||||||
|
- `pydantic` - for data models
|
||||||
|
- `python-dotenv` - already there but ensure proper use
|
||||||
|
- `httpx` - for async HTTP (future)
|
||||||
|
- Pin all versions with upper bounds for stability
|
||||||
|
|
||||||
|
Split into:
|
||||||
|
|
||||||
|
- `requirements.txt` - production dependencies
|
||||||
|
- `requirements-dev.txt` - development dependencies (pytest, black, mypy, etc.)
|
||||||
|
|
||||||
|
## Phase 7: Future Enhancements (Backlog)
|
||||||
|
|
||||||
|
### 7.1 Amenity Scoring System (Future Feature)
|
||||||
|
|
||||||
|
**New files: `nadlan_mcp/amenities.py`, Add tools to `fastmcp_server.py`**
|
||||||
|
|
||||||
|
Implement comprehensive amenity scoring using multiple data sources:
|
||||||
|
|
||||||
|
**Data Sources:**
|
||||||
|
|
||||||
|
- Google Places API / OpenStreetMap - basic amenity locations
|
||||||
|
- Ministry of Education - school rankings and quality metrics
|
||||||
|
- Ministry of Health - healthcare facility ratings
|
||||||
|
- CBS (Central Bureau of Statistics) - demographic data
|
||||||
|
- Public transport APIs - station locations and frequency
|
||||||
|
|
||||||
|
**New Functions:**
|
||||||
|
|
||||||
|
- `get_nearby_amenities(coordinates, radius, amenity_types)` - retrieve raw amenity data
|
||||||
|
- `get_school_quality_data(schools)` - fetch education quality metrics
|
||||||
|
- `calculate_amenity_score(address, weights)` - score with quality-weighted proximity
|
||||||
|
- `compare_amenity_scores(addresses)` - multi-address comparison
|
||||||
|
|
||||||
|
**Tools for LLM:**
|
||||||
|
|
||||||
|
- `get_address_amenity_rating` - comprehensive amenity analysis
|
||||||
|
- `compare_addresses_by_amenities` - side-by-side comparison
|
||||||
|
- `find_amenities_near_address` - raw amenity list with quality data
|
||||||
|
|
||||||
|
**Implementation approach:**
|
||||||
|
|
||||||
|
- Combine proximity with quality metrics (not just distance)
|
||||||
|
- Weight by category importance (configurable)
|
||||||
|
- Include static data sources for institutional quality
|
||||||
|
- Return both raw data and computed scores
|
||||||
|
|
||||||
|
### 7.2 Caching System (Future - Not in MVP)
|
||||||
|
|
||||||
|
**Task: In-Memory Caching**
|
||||||
|
|
||||||
|
- Add TTL-based caching for API responses
|
||||||
|
- Cache autocomplete results (1 hour TTL)
|
||||||
|
- Cache deal results (30 minute TTL)
|
||||||
|
- Implement cache invalidation strategy
|
||||||
|
- Add cache statistics/monitoring
|
||||||
|
|
||||||
|
**Task: Production-Ready Caching**
|
||||||
|
|
||||||
|
- Redis integration for distributed caching
|
||||||
|
- Cache warming strategies
|
||||||
|
- Cache synchronization across instances
|
||||||
|
- Persistent cache storage
|
||||||
|
- Cache versioning for schema changes
|
||||||
|
|
||||||
|
### 7.3 Performance Optimizations (Future)
|
||||||
|
|
||||||
|
**Task: Async/Parallel Processing**
|
||||||
|
|
||||||
|
- Convert to async/await for concurrent API calls
|
||||||
|
- Use `httpx` for async HTTP requests
|
||||||
|
- Parallel processing for multi-polygon queries
|
||||||
|
- Batch processing optimizations
|
||||||
|
|
||||||
|
**Task: Database Integration**
|
||||||
|
|
||||||
|
- SQLite for local development
|
||||||
|
- PostgreSQL for production
|
||||||
|
- Historical data tracking
|
||||||
|
- Faster query performance for trends
|
||||||
|
- Data warehouse for analytics
|
||||||
|
|
||||||
|
### 7.4 Multi-language Support (Future)
|
||||||
|
|
||||||
|
**Enhance language support:**
|
||||||
|
|
||||||
|
- Full English address support (currently Hebrew-focused)
|
||||||
|
- Translation services for property descriptions
|
||||||
|
- Language detection and auto-switching
|
||||||
|
- Multi-language documentation
|
||||||
|
|
||||||
|
### 7.5 Advanced Valuation Helper (Future)
|
||||||
|
|
||||||
|
**Optional calculation helper for LLM:**
|
||||||
|
|
||||||
|
**New Function:**
|
||||||
|
|
||||||
|
- `calculate_valuation_from_comparables(comparables, weights, adjustments)` - mathematical valuation helper
|
||||||
|
- Takes LLM-selected comparable deals
|
||||||
|
- Applies LLM-specified weights and adjustments
|
||||||
|
- Returns calculated price estimate with breakdown
|
||||||
|
- Pure calculation (no intelligence), LLM provides the logic
|
||||||
|
|
||||||
|
**Purpose:**
|
||||||
|
|
||||||
|
- LLM selects comparables and determines methodology
|
||||||
|
- Function just does the math reliably
|
||||||
|
- Returns detailed calculation breakdown for transparency
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
**High Priority (MVP):**
|
||||||
|
|
||||||
|
1. Phase 1: Code Quality & Reliability (critical for stability)
|
||||||
|
2. Phase 2: Missing Core Functionality (align with use cases)
|
||||||
|
3. Phase 6.1: Cleanup redundant code
|
||||||
|
|
||||||
|
**Medium Priority (Post-MVP):**
|
||||||
|
|
||||||
|
4. Phase 3: Architecture improvements (foundation for scaling)
|
||||||
|
5. Phase 4: Testing expansion (ensure quality)
|
||||||
|
6. Phase 5: Documentation (user experience)
|
||||||
|
|
||||||
|
**Low Priority (Future):**
|
||||||
|
|
||||||
|
7. Phase 6.2-6.3: Polish & optimization
|
||||||
|
8. Phase 7: Future enhancements (amenities, caching, async, multi-language)
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- ✅ All use cases in USECASES.md have corresponding implemented functions
|
||||||
|
- ✅ LLM can effectively use tools for all stated use cases
|
||||||
|
- ✅ Code is maintainable with <100 lines per function
|
||||||
|
- ✅ Test coverage >80% for core functionality
|
||||||
|
- ✅ All API calls have proper error handling and retries
|
||||||
|
- ✅ Documentation is comprehensive and up-to-date
|
||||||
|
- ✅ No duplicate or dead code
|
||||||
|
- ✅ Type hints on all public functions
|
||||||
|
- ✅ Configuration is externalized and documented
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
- **MCP provides data, LLM provides intelligence**: MCP retrieves and structures data; LLM analyzes, decides, and estimates
|
||||||
|
- **Structured by default**: Default responses should be detailed/structured for LLM processing
|
||||||
|
- **Optional summarization**: Add `summarized_response` parameter where helpful
|
||||||
|
- **No predictions in MVP**: Statistical aggregations yes, ML predictions no
|
||||||
|
- **External APIs for future**: Amenity scoring with quality metrics is a future enhancement
|
||||||
|
- **Quality over distance**: When amenities are added, include quality metrics not just proximity
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- No load testing on external Govmap API (as requested)
|
||||||
|
- Focus on LLM-friendly tool design: comprehensive data provision for LLM analysis
|
||||||
|
- Amenity scoring with quality data (Ministry of Education, etc.) is future feature
|
||||||
|
- Property valuation done by LLM, not MCP - MCP just provides comparable deals data
|
||||||
|
- Caching is explicitly not in MVP scope
|
||||||
|
- Keep functions focused: one function = one clear purpose
|
||||||
|
- Always return structured data that LLMs can easily process
|
||||||
|
- Use `summarized_response: bool = False` for optional summarization
|
||||||
|
|
||||||
|
## Implementation Tracking
|
||||||
|
|
||||||
|
**For detailed task tracking and current progress, see [TASKS.md](../../TASKS.md)**
|
||||||
|
|
||||||
|
### Completed (Phase 1)
|
||||||
|
- ✅ Configuration management system with environment variable support
|
||||||
|
- ✅ Retry logic with exponential backoff (manual implementation)
|
||||||
|
- ✅ Rate limiting protection
|
||||||
|
- ✅ Standardized error handling (raise exceptions)
|
||||||
|
- ✅ Comprehensive input validation
|
||||||
|
- ✅ Updated dependencies with version pinning
|
||||||
|
- ✅ Created requirements-dev.txt
|
||||||
|
- ✅ Updated USECASES.md with status indicators
|
||||||
|
- ✅ Created ARCHITECTURE.md
|
||||||
|
- ✅ Deleted redundant code files
|
||||||
|
|
||||||
|
### Completed (Phase 2.1)
|
||||||
|
- ✅ Implemented `filter_deals_by_criteria()` with comprehensive filtering
|
||||||
|
- ✅ Implemented `calculate_deal_statistics()` with statistical aggregations
|
||||||
|
- ✅ Added `get_valuation_comparables` MCP tool
|
||||||
|
- ✅ Added `get_deal_statistics` MCP tool
|
||||||
|
|
||||||
|
### In Progress
|
||||||
|
- 🚧 Phase 2.2: Market Activity & Investment Analysis
|
||||||
|
- 🚧 Phase 2.3: Enhanced Deal Filtering (partially complete)
|
||||||
|
|
||||||
|
### Next Up
|
||||||
|
- Phase 3: Architecture Improvements (data models, separation of concerns)
|
||||||
|
- Phase 4: Testing Expansion
|
||||||
|
- Phase 5: Complete Documentation
|
||||||
+5
-1
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
"MD032": false,
|
"MD032": false,
|
||||||
"MD022": false
|
"MD022": false,
|
||||||
|
"MD013": false,
|
||||||
|
"MD031": false,
|
||||||
|
"MD036": false,
|
||||||
|
"MD029": false
|
||||||
}
|
}
|
||||||
+490
@@ -0,0 +1,490 @@
|
|||||||
|
# Nadlan-MCP Architecture
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Nadlan-MCP is a Model Context Protocol (MCP) server that provides Israeli real estate data to AI agents (LLMs). The architecture follows a clean separation of concerns with three main layers:
|
||||||
|
|
||||||
|
1. **MCP Tools Layer** - Exposes functions to LLM clients
|
||||||
|
2. **Business Logic Layer** - Data analysis and processing
|
||||||
|
3. **API Client Layer** - Communicates with Govmap API
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
### 1. MCP Provides Data, LLM Provides Intelligence
|
||||||
|
|
||||||
|
The server's role is to retrieve, structure, and optionally summarize data. Complex analysis, decision-making, and predictions are left to the LLM client.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
- ✅ MCP: Provides comparable property deals with filters
|
||||||
|
- ✅ LLM: Analyzes comparables and estimates property value
|
||||||
|
- ❌ MCP: Calculates ML-based property valuation
|
||||||
|
|
||||||
|
### 2. Structured by Default
|
||||||
|
|
||||||
|
All tools return detailed, structured data by default. Optional `summarized_response` parameter provides condensed summaries when needed.
|
||||||
|
|
||||||
|
### 3. Reliability First
|
||||||
|
|
||||||
|
- Automatic retry with exponential backoff
|
||||||
|
- Rate limiting to respect API limits
|
||||||
|
- Comprehensive input validation
|
||||||
|
- Detailed error messages
|
||||||
|
|
||||||
|
### 4. Configuration Over Hard-coding
|
||||||
|
|
||||||
|
All settings (timeouts, retries, rate limits) are configurable via environment variables or code.
|
||||||
|
|
||||||
|
## System Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ LLM Client (AI Agent) │
|
||||||
|
│ (Claude, GPT, etc.) │
|
||||||
|
└────────────────────────────┬────────────────────────────────┘
|
||||||
|
│ MCP Protocol
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ MCP Tools Layer │
|
||||||
|
│ ┌───────────────┐ ┌─────────────────┐ ┌──────────────┐ │
|
||||||
|
│ │autocomplete │ │find_recent_deals│ │analyze_market│ │
|
||||||
|
│ │_address │ │_for_address │ │_trends │ │
|
||||||
|
│ └───────┬───────┘ └────────┬────────┘ └──────┬───────┘ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ┌───────┴───────────────────┴────────────────────┴──────┐ │
|
||||||
|
│ │ fastmcp_server.py (Tool Definitions) │ │
|
||||||
|
│ └───────────────────────────┬───────────────────────────┘ │
|
||||||
|
└────────────────────────────┬─┴────────────────────────────┘
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Business Logic Layer │
|
||||||
|
│ ┌────────────────────┐ ┌──────────────────────────────┐ │
|
||||||
|
│ │ Market Analysis │ │ Deal Processing & Filtering │ │
|
||||||
|
│ │ (analyze_market │ │ (_is_same_building, etc.) │ │
|
||||||
|
│ │ _trends logic) │ │ │ │
|
||||||
|
│ └────────────────────┘ └──────────────────────────────┘ │
|
||||||
|
│ ┌────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ govmap.py (GovmapClient) │ │
|
||||||
|
│ │ • Validation • Retry Logic • Rate Limiting │ │
|
||||||
|
│ └─────────────────────────┬──────────────────────────────┘ │
|
||||||
|
└───────────────────────────┬┴──────────────────────────────┘
|
||||||
|
│ HTTP/JSON
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Govmap API Layer │
|
||||||
|
│ (Israeli Government Real Estate API) │
|
||||||
|
│ ┌───────────────┐ ┌────────────┐ ┌──────────────────┐ │
|
||||||
|
│ │ Autocomplete │ │ Deals by │ │ Street/ │ │
|
||||||
|
│ │ Endpoint │ │ Radius │ │ Neighborhood │ │
|
||||||
|
│ │ │ │ Endpoint │ │ Deals Endpoints │ │
|
||||||
|
│ └───────────────┘ └────────────┘ └──────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Details
|
||||||
|
|
||||||
|
### Configuration Layer (`config.py`)
|
||||||
|
|
||||||
|
**Purpose:** Centralized configuration management
|
||||||
|
|
||||||
|
**Key Components:**
|
||||||
|
- `GovmapConfig` - Dataclass with all configuration settings
|
||||||
|
- Environment variable support for all settings
|
||||||
|
- Validation on initialization
|
||||||
|
|
||||||
|
**Configuration Options:**
|
||||||
|
- API settings (base URL, user agent)
|
||||||
|
- Timeout settings (connect, read)
|
||||||
|
- Retry settings (max retries, backoff)
|
||||||
|
- Rate limiting (requests per second)
|
||||||
|
- Default search parameters
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```python
|
||||||
|
from nadlan_mcp.config import get_config, set_config, GovmapConfig
|
||||||
|
|
||||||
|
# Use global config
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
|
# Override config
|
||||||
|
custom_config = GovmapConfig(max_retries=5, requests_per_second=10.0)
|
||||||
|
set_config(custom_config)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Client Layer (`govmap.py`)
|
||||||
|
|
||||||
|
**Purpose:** Communicate with Govmap API with reliability features
|
||||||
|
|
||||||
|
**Key Components:**
|
||||||
|
|
||||||
|
#### GovmapClient Class
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Make HTTP requests to Govmap API
|
||||||
|
- Implement retry logic with exponential backoff
|
||||||
|
- Enforce rate limiting
|
||||||
|
- Validate inputs
|
||||||
|
- Parse and validate responses
|
||||||
|
|
||||||
|
**Key Methods:**
|
||||||
|
- `autocomplete_address()` - Search for addresses
|
||||||
|
- `get_gush_helka()` - Get block/parcel data
|
||||||
|
- `get_deals_by_radius()` - Get nearby deals
|
||||||
|
- `get_street_deals()` - Get street-level deals
|
||||||
|
- `get_neighborhood_deals()` - Get neighborhood deals
|
||||||
|
- `find_recent_deals_for_address()` - Main comprehensive search
|
||||||
|
|
||||||
|
**Reliability Features:**
|
||||||
|
1. **Retry Logic** - Exponential backoff on failures
|
||||||
|
2. **Rate Limiting** - Tracks request times, enforces delays
|
||||||
|
3. **Input Validation** - Validates all inputs before API calls
|
||||||
|
4. **Timeouts** - Configurable connect and read timeouts
|
||||||
|
|
||||||
|
### MCP Tools Layer (`fastmcp_server.py`)
|
||||||
|
|
||||||
|
**Purpose:** Expose functionality to LLM clients via MCP protocol
|
||||||
|
|
||||||
|
**Key Components:**
|
||||||
|
- FastMCP server instance
|
||||||
|
- Tool definitions (decorated functions)
|
||||||
|
- JSON response formatting
|
||||||
|
- Error handling for user-friendly messages
|
||||||
|
|
||||||
|
**Tool Design Pattern:**
|
||||||
|
```python
|
||||||
|
@mcp.tool()
|
||||||
|
def tool_name(param: type, summarized_response: bool = False) -> str:
|
||||||
|
"""
|
||||||
|
Tool description for LLM.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: Parameter description
|
||||||
|
summarized_response:
|
||||||
|
- False (default): Full structured data for LLM processing
|
||||||
|
- True: Condensed summary with key insights
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON string with data or summary
|
||||||
|
"""
|
||||||
|
# 1. Call business logic / API client
|
||||||
|
# 2. Format response
|
||||||
|
# 3. Return JSON string
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
### Example: Find Recent Deals for Address
|
||||||
|
|
||||||
|
```
|
||||||
|
1. LLM Request
|
||||||
|
└─> "Find deals for Sokolov 38 Holon in last 2 years"
|
||||||
|
|
||||||
|
2. MCP Tool: find_recent_deals_for_address()
|
||||||
|
├─> Validate inputs
|
||||||
|
└─> Call GovmapClient
|
||||||
|
|
||||||
|
3. GovmapClient Workflow:
|
||||||
|
├─> autocomplete_address("סוקולוב 38 חולון")
|
||||||
|
│ ├─> Rate limit check
|
||||||
|
│ ├─> HTTP POST (with retry)
|
||||||
|
│ └─> Parse coordinates
|
||||||
|
│
|
||||||
|
├─> get_deals_by_radius(coordinates, 30m)
|
||||||
|
│ ├─> Rate limit check
|
||||||
|
│ ├─> HTTP GET (with retry)
|
||||||
|
│ └─> Extract polygon_ids
|
||||||
|
│
|
||||||
|
├─> For each polygon_id:
|
||||||
|
│ ├─> get_street_deals(polygon_id)
|
||||||
|
│ └─> get_neighborhood_deals(polygon_id)
|
||||||
|
│
|
||||||
|
├─> Filter & Prioritize:
|
||||||
|
│ ├─> Same building deals (priority 0)
|
||||||
|
│ ├─> Street deals (priority 1)
|
||||||
|
│ └─> Neighborhood deals (priority 2)
|
||||||
|
│
|
||||||
|
└─> Sort by priority then date
|
||||||
|
|
||||||
|
4. Format Response
|
||||||
|
└─> JSON with deals + metadata
|
||||||
|
|
||||||
|
5. Return to LLM
|
||||||
|
└─> LLM analyzes and responds to user
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling Strategy
|
||||||
|
|
||||||
|
### Validation Errors (ValueError)
|
||||||
|
- Invalid address format
|
||||||
|
- Negative/zero parameters
|
||||||
|
- Out-of-range values
|
||||||
|
- **Action:** Immediate failure with clear message
|
||||||
|
|
||||||
|
### Network Errors (requests.RequestException)
|
||||||
|
- Connection failures
|
||||||
|
- Timeouts
|
||||||
|
- HTTP errors
|
||||||
|
- **Action:** Retry with exponential backoff (up to configured max_retries)
|
||||||
|
|
||||||
|
### API Response Errors
|
||||||
|
- Invalid JSON
|
||||||
|
- Unexpected format
|
||||||
|
- Missing required fields
|
||||||
|
- **Action:** Raise ValueError with specific details
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
- Track last request time
|
||||||
|
- Sleep if necessary before request
|
||||||
|
- **Action:** Transparent to caller, automatic
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Current Implementation
|
||||||
|
|
||||||
|
**Optimizations:**
|
||||||
|
- Connection pooling (requests.Session)
|
||||||
|
- Rate limiting prevents API throttling
|
||||||
|
- Early validation reduces unnecessary API calls
|
||||||
|
- Polygon deduplication in find_recent_deals_for_address()
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- Synchronous (blocking) API calls
|
||||||
|
- No caching (MVP decision)
|
||||||
|
- Sequential polygon queries
|
||||||
|
|
||||||
|
### Future Optimizations (Not in MVP)
|
||||||
|
|
||||||
|
**Phase 1: In-Memory Caching**
|
||||||
|
- Cache autocomplete results (1 hour TTL)
|
||||||
|
- Cache deal results (30 minute TTL)
|
||||||
|
- Reduce API load by 60-80%
|
||||||
|
|
||||||
|
**Phase 2: Async/Parallel**
|
||||||
|
- Convert to async/await
|
||||||
|
- Parallel polygon queries
|
||||||
|
- 3-5x faster for multi-polygon searches
|
||||||
|
|
||||||
|
**Phase 3: Production Caching**
|
||||||
|
- Redis for distributed caching
|
||||||
|
- Cache warming strategies
|
||||||
|
- Cross-instance consistency
|
||||||
|
|
||||||
|
## Security & Rate Limiting
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
|
||||||
|
**Current:** 5 requests/second (configurable)
|
||||||
|
|
||||||
|
**Rationale:**
|
||||||
|
- Respects Govmap API (no published limits, being conservative)
|
||||||
|
- Prevents accidental DoS
|
||||||
|
- Balances responsiveness with safety
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Tracks last request timestamp
|
||||||
|
- Sleeps if interval too short
|
||||||
|
- Per-instance (not distributed)
|
||||||
|
|
||||||
|
### Input Validation
|
||||||
|
|
||||||
|
All user inputs are validated before use:
|
||||||
|
- Address strings: length, type, non-empty
|
||||||
|
- Coordinates: numeric, reasonable bounds
|
||||||
|
- Integers: positive, max values
|
||||||
|
- Deal types: enum validation (1 or 2)
|
||||||
|
|
||||||
|
**Prevents:**
|
||||||
|
- Injection attacks
|
||||||
|
- API errors from malformed requests
|
||||||
|
- Expensive/dangerous operations
|
||||||
|
|
||||||
|
### API Key Management
|
||||||
|
|
||||||
|
**Current:** No API keys required (public API)
|
||||||
|
|
||||||
|
**Future:** If API keys added:
|
||||||
|
- Store in environment variables only
|
||||||
|
- Never log or expose in responses
|
||||||
|
- Rotate regularly
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
- Mock API responses
|
||||||
|
- Test validation logic
|
||||||
|
- Test error handling
|
||||||
|
- Test retry logic
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- Real API calls (marked `@pytest.mark.integration`)
|
||||||
|
- VCR.py for recording/replaying
|
||||||
|
- Test complete workflows
|
||||||
|
|
||||||
|
### Validation Tests
|
||||||
|
- Invalid inputs
|
||||||
|
- Edge cases
|
||||||
|
- Boundary conditions
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API Settings
|
||||||
|
GOVMAP_BASE_URL=https://www.govmap.gov.il/api/
|
||||||
|
GOVMAP_USER_AGENT=NadlanMCP/1.0.0
|
||||||
|
|
||||||
|
# Timeouts (seconds)
|
||||||
|
GOVMAP_CONNECT_TIMEOUT=10
|
||||||
|
GOVMAP_READ_TIMEOUT=30
|
||||||
|
|
||||||
|
# Retry Settings
|
||||||
|
GOVMAP_MAX_RETRIES=3
|
||||||
|
GOVMAP_RETRY_MIN_WAIT=1
|
||||||
|
GOVMAP_RETRY_MAX_WAIT=10
|
||||||
|
|
||||||
|
# Rate Limiting
|
||||||
|
GOVMAP_REQUESTS_PER_SECOND=5.0
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
GOVMAP_DEFAULT_RADIUS=50
|
||||||
|
GOVMAP_DEFAULT_YEARS_BACK=2
|
||||||
|
GOVMAP_DEFAULT_DEAL_LIMIT=100
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tuning Guidelines
|
||||||
|
|
||||||
|
**High Reliability (Production):**
|
||||||
|
- `MAX_RETRIES=5`
|
||||||
|
- `RETRY_MAX_WAIT=30`
|
||||||
|
- `REQUESTS_PER_SECOND=3.0`
|
||||||
|
|
||||||
|
**High Performance (Development):**
|
||||||
|
- `MAX_RETRIES=2`
|
||||||
|
- `RETRY_MAX_WAIT=5`
|
||||||
|
- `REQUESTS_PER_SECOND=10.0`
|
||||||
|
|
||||||
|
**Conservative (Shared API):**
|
||||||
|
- `MAX_RETRIES=3`
|
||||||
|
- `RETRY_MAX_WAIT=10`
|
||||||
|
- `REQUESTS_PER_SECOND=2.0`
|
||||||
|
|
||||||
|
## Future Architecture Evolution
|
||||||
|
|
||||||
|
### Phase 2: Data Models (Pydantic)
|
||||||
|
|
||||||
|
Add structured models for type safety:
|
||||||
|
```python
|
||||||
|
class Deal(BaseModel):
|
||||||
|
deal_id: str
|
||||||
|
address: str
|
||||||
|
deal_amount: float
|
||||||
|
asset_area: float
|
||||||
|
price_per_sqm: float
|
||||||
|
deal_date: datetime
|
||||||
|
property_type: str
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Separation of Concerns
|
||||||
|
|
||||||
|
```
|
||||||
|
nadlan_mcp/
|
||||||
|
├── api_client.py # Pure API communication
|
||||||
|
├── analyzers/
|
||||||
|
│ ├── market.py # Market analysis logic
|
||||||
|
│ ├── valuation.py # Valuation helpers
|
||||||
|
│ └── filtering.py # Deal filtering
|
||||||
|
├── models.py # Pydantic models
|
||||||
|
└── fastmcp_server.py # Thin MCP tool definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Database Layer (Optional)
|
||||||
|
|
||||||
|
For historical tracking and faster queries:
|
||||||
|
```
|
||||||
|
├── database/
|
||||||
|
│ ├── models.py # SQLAlchemy models
|
||||||
|
│ ├── crud.py # CRUD operations
|
||||||
|
│ └── migrations/ # Alembic migrations
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
python run_fastmcp_server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production (MCP Client)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"servers": {
|
||||||
|
"nadlan-mcp": {
|
||||||
|
"command": "python",
|
||||||
|
"args": ["/path/to/nadlan-mcp/run_fastmcp_server.py"],
|
||||||
|
"env": {
|
||||||
|
"GOVMAP_REQUESTS_PER_SECOND": "3.0",
|
||||||
|
"GOVMAP_MAX_RETRIES": "5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Limitations
|
||||||
|
|
||||||
|
### Govmap API Constraints
|
||||||
|
|
||||||
|
**Known Limitations:**
|
||||||
|
- No published rate limits (being conservative with 5 req/sec)
|
||||||
|
- No authentication required (public API)
|
||||||
|
- Data freshness: Updated periodically (not real-time)
|
||||||
|
- Coverage: Official Israeli government records only
|
||||||
|
|
||||||
|
**Best Practices:**
|
||||||
|
- Use appropriate time ranges (2-5 years recommended)
|
||||||
|
- Limit radius searches (under 1000m for performance)
|
||||||
|
- Cache results when possible (future feature)
|
||||||
|
- Respect rate limiting
|
||||||
|
|
||||||
|
### MCP Protocol Constraints
|
||||||
|
|
||||||
|
**Token Limits:**
|
||||||
|
- Large deal lists can exceed LLM context windows
|
||||||
|
- Use `summarized_response=True` for large datasets
|
||||||
|
- Default limits (50-100 deals) are token-optimized
|
||||||
|
|
||||||
|
## Monitoring & Debugging
|
||||||
|
|
||||||
|
### Logging Levels
|
||||||
|
|
||||||
|
**INFO:** Normal operations
|
||||||
|
- Requests being made
|
||||||
|
- Successful responses
|
||||||
|
|
||||||
|
**WARNING:** Recoverable issues
|
||||||
|
- Retrying after failures
|
||||||
|
- Rate limiting delays
|
||||||
|
- Validation warnings
|
||||||
|
|
||||||
|
**ERROR:** Failures
|
||||||
|
- Exhausted retries
|
||||||
|
- Invalid responses
|
||||||
|
- Configuration errors
|
||||||
|
|
||||||
|
### Debug Mode
|
||||||
|
|
||||||
|
Enable debug logging:
|
||||||
|
```python
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Govmap API](https://www.govmap.gov.il/)
|
||||||
|
- [MCP Protocol](https://modelcontextprotocol.io/)
|
||||||
|
- [FastMCP Documentation](https://github.com/jlowin/fastmcp)
|
||||||
|
- [Israeli Real Estate Data](https://data.gov.il/)
|
||||||
|
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
# Nadlan-MCP Implementation Tasks
|
||||||
|
|
||||||
|
This document tracks the implementation progress of the Nadlan-MCP improvement plan.
|
||||||
|
|
||||||
|
## ✅ Completed Tasks
|
||||||
|
|
||||||
|
### Phase 1: Code Quality & Reliability
|
||||||
|
- ✅ Created configuration management system (`config.py`)
|
||||||
|
- ✅ Added retry logic with exponential backoff
|
||||||
|
- ✅ Implemented rate limiting protection
|
||||||
|
- ✅ Standardized error handling (raise exceptions, not return empty lists)
|
||||||
|
- ✅ Added comprehensive input validation
|
||||||
|
- ✅ Updated requirements.txt with pinned versions
|
||||||
|
- ✅ Created requirements-dev.txt for development dependencies
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- ✅ Updated USECASES.md with status indicators and roadmap
|
||||||
|
- ✅ Created ARCHITECTURE.md with system design documentation
|
||||||
|
- ✅ Marked amenity scoring as future feature with clear roadmap
|
||||||
|
|
||||||
|
### Cleanup
|
||||||
|
- ✅ Deleted redundant mcp_server_concept.py file
|
||||||
|
|
||||||
|
### Phase 2.1: Property Valuation Data Provision
|
||||||
|
- ✅ Created `filter_deals_by_criteria()` function with comprehensive filtering
|
||||||
|
- ✅ Created `calculate_deal_statistics()` helper for statistical aggregations
|
||||||
|
- ✅ Created `_extract_floor_number()` helper for Hebrew floor parsing
|
||||||
|
- ✅ Created `_calculate_std_dev()` for standard deviation
|
||||||
|
- ✅ Added MCP tool: `get_valuation_comparables`
|
||||||
|
- ✅ Added MCP tool: `get_deal_statistics`
|
||||||
|
|
||||||
|
## 🚧 In Progress
|
||||||
|
|
||||||
|
### Phase 2: Missing Core Functionality
|
||||||
|
|
||||||
|
#### 2.2 Market Activity & Investment Analysis
|
||||||
|
- [ ] Create `market_analysis.py` module
|
||||||
|
- [ ] Implement `calculate_market_activity_score()`
|
||||||
|
- [ ] Implement `analyze_investment_potential()`
|
||||||
|
- [ ] Implement `get_market_liquidity()`
|
||||||
|
- [ ] Add MCP tools for market analysis
|
||||||
|
- [ ] Update documentation
|
||||||
|
|
||||||
|
#### 2.3 Enhanced Deal Filtering & Search
|
||||||
|
- ✅ Implement `filter_deals_by_criteria()` in govmap.py
|
||||||
|
- ✅ Add property type filtering
|
||||||
|
- ✅ Add room count filtering
|
||||||
|
- ✅ Add price range filtering
|
||||||
|
- ✅ Add area range filtering
|
||||||
|
- ✅ Add floor range filtering (with Hebrew floor name parsing)
|
||||||
|
- [ ] Update existing functions to support new filters (integration)
|
||||||
|
- [ ] Add tests for filtering logic
|
||||||
|
|
||||||
|
## 📋 To-Do (Next Priority)
|
||||||
|
|
||||||
|
### Phase 3: Architecture Improvements
|
||||||
|
|
||||||
|
#### 3.1 Data Models
|
||||||
|
- [ ] Create `models.py` with Pydantic models
|
||||||
|
- [ ] Deal model
|
||||||
|
- [ ] Address model
|
||||||
|
- [ ] MarketAnalysis model
|
||||||
|
- [ ] PropertyValuation model
|
||||||
|
- [ ] Filter models (DealFilters, etc.)
|
||||||
|
- [ ] Update functions to use models
|
||||||
|
- [ ] Add model validation tests
|
||||||
|
|
||||||
|
#### 3.2 Separation of Concerns
|
||||||
|
- [ ] Refactor fastmcp_server.py:
|
||||||
|
- [ ] Move analysis logic to dedicated modules
|
||||||
|
- [ ] Keep only MCP tool definitions in fastmcp_server.py
|
||||||
|
- [ ] Create `api_client.py` for pure API interactions
|
||||||
|
- [ ] Create `analyzers/` package:
|
||||||
|
- [ ] `analyzers/market.py` - Market analysis functions
|
||||||
|
- [ ] `analyzers/filtering.py` - Deal filtering logic
|
||||||
|
- [ ] `analyzers/valuation.py` - Valuation helpers
|
||||||
|
- [ ] Update imports and dependencies
|
||||||
|
- [ ] Update tests
|
||||||
|
|
||||||
|
#### 3.3 LLM-Friendly Tool Design
|
||||||
|
- [ ] Add `summarized_response: bool = False` parameter to all tools
|
||||||
|
- [ ] Implement summarization logic for each tool
|
||||||
|
- [ ] Update tool docstrings with parameter descriptions
|
||||||
|
- [ ] Test both modes (structured and summarized)
|
||||||
|
- [ ] Update documentation with examples
|
||||||
|
|
||||||
|
### Phase 4: Testing & Quality
|
||||||
|
|
||||||
|
#### 4.1 Expand Test Coverage
|
||||||
|
- [ ] Add integration tests (with @pytest.mark.integration)
|
||||||
|
- [ ] Add edge case tests for all functions
|
||||||
|
- [ ] Add parametrized tests for address formats
|
||||||
|
- [ ] Add tests for new valuation tools
|
||||||
|
- [ ] Add tests for market analysis tools
|
||||||
|
- [ ] Add tests for enhanced filtering
|
||||||
|
- [ ] Add tests for `analyze_market_trends`
|
||||||
|
- [ ] Add tests for `compare_addresses`
|
||||||
|
- [ ] Add tests for `_is_same_building` logic
|
||||||
|
|
||||||
|
#### 4.2 Validation Tests
|
||||||
|
- [ ] Create `tests/test_validation.py`
|
||||||
|
- [ ] Test address validation
|
||||||
|
- [ ] Test coordinate validation
|
||||||
|
- [ ] Test integer validation
|
||||||
|
- [ ] Test configuration validation
|
||||||
|
- [ ] Test model validation (Pydantic)
|
||||||
|
|
||||||
|
#### 4.3 Mock External APIs
|
||||||
|
- [ ] Update `tests/conftest.py` with comprehensive fixtures
|
||||||
|
- [ ] Add VCR.py for recording/replaying API calls
|
||||||
|
- [ ] Create fixture for deal responses
|
||||||
|
- [ ] Create fixture for autocomplete responses
|
||||||
|
- [ ] Create fixture for error scenarios
|
||||||
|
|
||||||
|
### Phase 5: Documentation
|
||||||
|
|
||||||
|
#### 5.1 Additional Documentation Files
|
||||||
|
- [ ] Create `DEPLOYMENT.md` - Deployment guide
|
||||||
|
- [ ] Create `CONTRIBUTING.md` - Contribution guidelines
|
||||||
|
- [ ] Create `API_REFERENCE.md` - Detailed API docs
|
||||||
|
- [ ] Create `CLAUDE.md` - Instructions for AI coding agents
|
||||||
|
- [ ] Create `docs/` directory for additional docs
|
||||||
|
|
||||||
|
#### 5.2 Code Documentation
|
||||||
|
- [ ] Add module-level docstrings to all Python files
|
||||||
|
- [ ] Enhance function docstrings with examples
|
||||||
|
- [ ] Add type hints to remaining functions
|
||||||
|
- [ ] Add inline comments for complex logic
|
||||||
|
- [ ] Review and improve existing documentation
|
||||||
|
|
||||||
|
#### 5.3 Usage Examples
|
||||||
|
- [ ] Create `examples/` directory
|
||||||
|
- [ ] Create `examples/basic_search.py`
|
||||||
|
- [ ] Create `examples/market_analysis.py`
|
||||||
|
- [ ] Create `examples/investment_analysis.py`
|
||||||
|
- [ ] Create `examples/llm_integration.py`
|
||||||
|
- [ ] Add README in examples/ directory
|
||||||
|
|
||||||
|
#### 5.4 README Updates
|
||||||
|
- [ ] Update README.md with current feature list
|
||||||
|
- [ ] Add configuration documentation
|
||||||
|
- [ ] Add troubleshooting section
|
||||||
|
- [ ] Add API limitations section
|
||||||
|
- [ ] Add examples from examples/ directory
|
||||||
|
|
||||||
|
### Phase 6: Code Quality & Polish
|
||||||
|
|
||||||
|
#### 6.2 Code Style & Linting
|
||||||
|
- [ ] Create `.pre-commit-config.yaml`
|
||||||
|
- [ ] Setup black formatter
|
||||||
|
- [ ] Setup isort for imports
|
||||||
|
- [ ] Setup flake8 linter
|
||||||
|
- [ ] Setup mypy for type checking
|
||||||
|
- [ ] Format all code with black
|
||||||
|
- [ ] Sort all imports with isort
|
||||||
|
- [ ] Fix all flake8 warnings
|
||||||
|
- [ ] Fix all mypy errors
|
||||||
|
- [ ] Add pre-commit hooks to CI
|
||||||
|
|
||||||
|
#### 6.3 Remaining Cleanup
|
||||||
|
- [ ] Remove any remaining unused imports
|
||||||
|
- [ ] Consolidate duplicate code
|
||||||
|
- [ ] Refactor long functions (>100 lines)
|
||||||
|
- [ ] Improve naming consistency
|
||||||
|
|
||||||
|
## 🔮 Future Features (Backlog)
|
||||||
|
|
||||||
|
### Phase 7.1: Amenity Scoring
|
||||||
|
- [ ] Research Google Places API integration
|
||||||
|
- [ ] Research OpenStreetMap integration
|
||||||
|
- [ ] Research Ministry of Education data sources
|
||||||
|
- [ ] Research Ministry of Health data sources
|
||||||
|
- [ ] Design amenity scoring algorithm
|
||||||
|
- [ ] Implement `amenities.py` module
|
||||||
|
- [ ] Add amenity MCP tools
|
||||||
|
- [ ] Add amenity tests
|
||||||
|
- [ ] Document amenity scoring methodology
|
||||||
|
|
||||||
|
### Phase 7.2: Caching System
|
||||||
|
- [ ] Design caching strategy
|
||||||
|
- [ ] Implement in-memory cache with TTL
|
||||||
|
- [ ] Add cache configuration options
|
||||||
|
- [ ] Add cache statistics/monitoring
|
||||||
|
- [ ] Test cache invalidation
|
||||||
|
- [ ] Document caching behavior
|
||||||
|
- [ ] (Later) Implement Redis integration
|
||||||
|
- [ ] (Later) Add cache warming
|
||||||
|
|
||||||
|
### Phase 7.3: Performance Optimizations
|
||||||
|
- [ ] Research async/await patterns
|
||||||
|
- [ ] Convert to async HTTP with httpx
|
||||||
|
- [ ] Implement parallel polygon queries
|
||||||
|
- [ ] Add performance benchmarks
|
||||||
|
- [ ] Optimize token usage in responses
|
||||||
|
- [ ] (Later) Database integration design
|
||||||
|
- [ ] (Later) SQLite implementation
|
||||||
|
- [ ] (Later) PostgreSQL migration path
|
||||||
|
|
||||||
|
### Phase 7.4: Multi-language Support
|
||||||
|
- [ ] Add English address support
|
||||||
|
- [ ] Add translation service integration
|
||||||
|
- [ ] Implement language detection
|
||||||
|
- [ ] Update documentation for multiple languages
|
||||||
|
- [ ] Add language selection parameter
|
||||||
|
|
||||||
|
### Phase 7.5: Advanced Valuation Helper
|
||||||
|
- [ ] Design calculation algorithm
|
||||||
|
- [ ] Implement `calculate_valuation_from_comparables()`
|
||||||
|
- [ ] Add detailed breakdown in response
|
||||||
|
- [ ] Test calculation accuracy
|
||||||
|
- [ ] Document methodology
|
||||||
|
|
||||||
|
## 📊 Progress Summary
|
||||||
|
|
||||||
|
**Overall Progress:** ~40% complete
|
||||||
|
|
||||||
|
### By Phase:
|
||||||
|
- Phase 1 (Code Quality): ✅ 100% complete
|
||||||
|
- Phase 2.1 (Valuation Data): ✅ 100% complete
|
||||||
|
- Phase 2.2 (Market Analysis): 📋 0% started
|
||||||
|
- Phase 2.3 (Enhanced Filtering): ✅ 85% complete (integration & tests pending)
|
||||||
|
- Phase 3 (Architecture): 📋 0% started
|
||||||
|
- Phase 4 (Testing): 📋 0% started
|
||||||
|
- Phase 5 (Documentation): 🚧 30% complete (USECASES, ARCHITECTURE, TASKS done)
|
||||||
|
- Phase 6 (Polish): 🚧 33% complete (cleanup done, linting pending)
|
||||||
|
- Phase 7 (Future): 📋 Backlog
|
||||||
|
|
||||||
|
### High Priority (MVP) Status:
|
||||||
|
- ✅ Phase 1: Code Quality & Reliability - COMPLETE
|
||||||
|
- ✅ Phase 2.1: Property Valuation Data Provision - COMPLETE
|
||||||
|
- 🚧 Phase 2.2: Market Analysis - NEXT PRIORITY
|
||||||
|
- 🚧 Phase 2.3: Enhanced Filtering - MOSTLY COMPLETE (tests pending)
|
||||||
|
|
||||||
|
## 🎯 Current Sprint Focus
|
||||||
|
|
||||||
|
1. ✅ **Implement valuation data provision tools** (Phase 2.1) - COMPLETE
|
||||||
|
2. **Implement market analysis tools** (Phase 2.2) - NEXT
|
||||||
|
3. ✅ **Implement enhanced filtering** (Phase 2.3) - MOSTLY COMPLETE
|
||||||
|
|
||||||
|
## 🎯 Next Sprint
|
||||||
|
|
||||||
|
1. **Implement market activity metrics** (Phase 2.2)
|
||||||
|
2. **Add tests for new filtering and valuation features** (Phase 4.1)
|
||||||
|
3. **Create additional documentation files** (Phase 5.1)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All configuration is now externalized and documented
|
||||||
|
- Error handling is robust with retry logic
|
||||||
|
- Documentation is aligned with actual implementation
|
||||||
|
- Ready to add new features on solid foundation
|
||||||
|
|
||||||
+103
-26
@@ -2,54 +2,81 @@
|
|||||||
|
|
||||||
The nadlan-mcp MCP server provides comprehensive Israeli real estate data analysis capabilities. Here's what you can do with it:
|
The nadlan-mcp MCP server provides comprehensive Israeli real estate data analysis capabilities. Here's what you can do with it:
|
||||||
|
|
||||||
## 🏠 **Address & Location Services**
|
**Legend:**
|
||||||
|
- ✅ **Implemented**: Feature is fully available
|
||||||
|
- 🚧 **In Progress**: Feature is currently being developed
|
||||||
|
- 📋 **Planned**: Feature is planned for future release
|
||||||
|
|
||||||
|
## 🏠 **Address & Location Services** ✅
|
||||||
|
|
||||||
- **Address Autocomplete**: Search and autocomplete Israeli addresses (supports both Hebrew and English)
|
- **Address Autocomplete**: Search and autocomplete Israeli addresses (supports both Hebrew and English)
|
||||||
- **Location-based Deals**: Find real estate deals within a specific radius of any coordinates
|
- **Location-based Deals**: Find real estate deals within a specific radius of any coordinates
|
||||||
|
|
||||||
## 📊 **Real Estate Deal Analysis**
|
## 📊 **Real Estate Deal Analysis** ✅
|
||||||
|
|
||||||
- **Recent Deals by Address**: Find recent real estate transactions for any specific address
|
- **Recent Deals by Address**: Find recent real estate transactions for any specific address
|
||||||
- **Street-level Analysis**: Get all recent deals for an entire street/area
|
- **Street-level Analysis**: Get all recent deals for an entire street/area
|
||||||
- **Neighborhood Analysis**: Analyze deals across entire neighborhoods
|
- **Neighborhood Analysis**: Analyze deals across entire neighborhoods
|
||||||
|
- **Deal Filtering**: Filter deals by property type, room count, price range, area, and floor (🚧 enhanced filtering in progress)
|
||||||
|
|
||||||
|
## 📈 **Market Intelligence** ✅
|
||||||
|
|
||||||
## 📈 **Market Intelligence**
|
|
||||||
- **Market Trends Analysis**: Analyze price patterns and market trends over time for any area
|
- **Market Trends Analysis**: Analyze price patterns and market trends over time for any area
|
||||||
- **Price per Square Meter Trends**: Track how property values change over time
|
- **Price per Square Meter Trends**: Track how property values change over time
|
||||||
- **Market Activity Levels**: See how active the real estate market is in different areas
|
- **Market Activity Levels**: See how active the real estate market is in different areas (🚧 enhanced metrics in progress)
|
||||||
|
|
||||||
|
## 🔍 **Comparative Analysis** ✅
|
||||||
|
|
||||||
## 🔍 **Comparative Analysis**
|
|
||||||
- **Multi-Address Comparison**: Compare real estate markets between multiple addresses side-by-side
|
- **Multi-Address Comparison**: Compare real estate markets between multiple addresses side-by-side
|
||||||
- **Investment Analysis**: Compare different areas for investment potential
|
- **Investment Analysis**: Compare different areas for investment potential (🚧 advanced metrics in progress)
|
||||||
|
|
||||||
## 🏆 **Amenity Scoring & Quality of Life Analysis**
|
## 🏆 **Amenity Scoring & Quality of Life Analysis** 📋
|
||||||
- **Address Amenity Rating**: Score individual addresses based on proximity to essential amenities
|
|
||||||
|
**Status: Planned for Future Release**
|
||||||
|
|
||||||
|
This comprehensive feature will provide amenity-based location scoring using multiple data sources:
|
||||||
|
|
||||||
|
- **Address Amenity Rating**: Score individual addresses based on proximity AND quality of essential amenities
|
||||||
- **Street-level Amenity Analysis**: Rate entire streets by their access to schools, parks, healthcare, and other services
|
- **Street-level Amenity Analysis**: Rate entire streets by their access to schools, parks, healthcare, and other services
|
||||||
- **Neighborhood Quality Scoring**: Comprehensive neighborhood ratings based on amenity density and accessibility
|
- **Neighborhood Quality Scoring**: Comprehensive neighborhood ratings based on amenity density, accessibility, and quality
|
||||||
- **Lifestyle Compatibility**: Match addresses to lifestyle preferences based on nearby amenities
|
- **Lifestyle Compatibility**: Match addresses to lifestyle preferences based on nearby amenities
|
||||||
|
|
||||||
|
**Data Sources (Planned):**
|
||||||
|
- Google Places API / OpenStreetMap - amenity locations
|
||||||
|
- Ministry of Education - school rankings and quality metrics
|
||||||
|
- Ministry of Health - healthcare facility ratings
|
||||||
|
- CBS (Central Bureau of Statistics) - demographic data
|
||||||
|
- Public transport APIs - station locations and service frequency
|
||||||
|
|
||||||
|
**Note:** This feature will combine proximity with quality metrics (not just distance) to provide meaningful amenity scores.
|
||||||
|
|
||||||
## 💡 **Practical Use Cases**
|
## 💡 **Practical Use Cases**
|
||||||
|
|
||||||
### Property Valuation
|
### Property Valuation ✅
|
||||||
- Research recent sales to estimate property values
|
- Research recent sales to estimate property values
|
||||||
- Understand pricing trends in specific neighborhoods
|
- Understand pricing trends in specific neighborhoods
|
||||||
- Compare similar properties in the area
|
- Compare similar properties in the area
|
||||||
|
- **Note:** The MCP provides comprehensive data; the LLM performs the valuation analysis
|
||||||
|
|
||||||
### Investment Research
|
### Investment Research ✅
|
||||||
- Identify trending neighborhoods and price patterns
|
- Identify trending neighborhoods and price patterns
|
||||||
- Analyze market activity levels across different areas
|
- Analyze market activity levels across different areas (🚧 enhanced metrics in progress)
|
||||||
- Track price per square meter trends over time
|
- Track price per square meter trends over time
|
||||||
|
|
||||||
### Market Analysis
|
### Market Analysis ✅
|
||||||
- Understand local real estate dynamics
|
- Understand local real estate dynamics
|
||||||
- Monitor market trends and patterns
|
- Monitor market trends and patterns
|
||||||
- Identify emerging or declining areas
|
- Identify emerging or declining areas
|
||||||
|
|
||||||
### Due Diligence
|
### Due Diligence ✅
|
||||||
- Research an area before buying/selling property
|
- Research an area before buying/selling property
|
||||||
- Understand recent transaction history
|
- Understand recent transaction history
|
||||||
- Compare multiple potential locations
|
- Compare multiple potential locations
|
||||||
|
|
||||||
### Amenity-Based Location Selection
|
### Amenity-Based Location Selection 📋
|
||||||
- Score addresses based on proximity to schools, parks, healthcare facilities
|
**Planned for Future Release**
|
||||||
|
|
||||||
|
- Score addresses based on proximity AND quality of schools, parks, healthcare facilities
|
||||||
- Find family-friendly neighborhoods with high amenity scores
|
- Find family-friendly neighborhoods with high amenity scores
|
||||||
- Identify areas with the best access to public transportation and services
|
- Identify areas with the best access to public transportation and services
|
||||||
- Compare lifestyle compatibility across different locations
|
- Compare lifestyle compatibility across different locations
|
||||||
@@ -63,13 +90,15 @@ You can ask questions like:
|
|||||||
- "Find all recent sales on [street name]"
|
- "Find all recent sales on [street name]"
|
||||||
- "Analyze market trends around [specific address]"
|
- "Analyze market trends around [specific address]"
|
||||||
- "What's the average price per square meter in [area]?"
|
- "What's the average price per square meter in [area]?"
|
||||||
- "Rate this address based on nearby amenities like schools and parks"
|
- "Rate this address based on nearby amenities like schools and parks" *(📋 Planned)*
|
||||||
- "Which street has better access to healthcare and education facilities?"
|
- "Which street has better access to healthcare and education facilities?" *(📋 Planned)*
|
||||||
- "Score this neighborhood for family-friendliness based on amenities"
|
- "Score this neighborhood for family-friendliness based on amenities" *(📋 Planned)*
|
||||||
- "Compare amenity scores between these two addresses"
|
- "Compare amenity scores between these two addresses" *(📋 Planned)*
|
||||||
|
|
||||||
## 📋 **Available Functions**
|
## 📋 **Available Functions**
|
||||||
|
|
||||||
|
### ✅ Implemented
|
||||||
|
|
||||||
- `autocomplete_address` - Search and autocomplete Israeli addresses
|
- `autocomplete_address` - Search and autocomplete Israeli addresses
|
||||||
- `get_deals_by_radius` - Get deals within a radius of coordinates
|
- `get_deals_by_radius` - Get deals within a radius of coordinates
|
||||||
- `get_street_deals` - Get deals for a specific street polygon
|
- `get_street_deals` - Get deals for a specific street polygon
|
||||||
@@ -78,16 +107,64 @@ You can ask questions like:
|
|||||||
- `analyze_market_trends` - Analyze market trends and price patterns
|
- `analyze_market_trends` - Analyze market trends and price patterns
|
||||||
- `compare_addresses` - Compare real estate markets between multiple addresses
|
- `compare_addresses` - Compare real estate markets between multiple addresses
|
||||||
|
|
||||||
|
### 🚧 In Progress
|
||||||
|
|
||||||
|
- `get_valuation_comparables` - Get comparable properties for valuation analysis
|
||||||
|
- `get_deal_statistics` - Calculate statistical aggregations on deal data
|
||||||
|
- `get_market_activity_metrics` - Detailed market activity and velocity metrics
|
||||||
|
- Enhanced filtering for all deal functions (property type, rooms, price range, area, floor)
|
||||||
|
|
||||||
|
### 📋 Planned (Future Release)
|
||||||
|
|
||||||
|
- `get_address_amenity_rating` - Comprehensive amenity analysis with quality metrics
|
||||||
|
- `compare_addresses_by_amenities` - Side-by-side amenity comparison
|
||||||
|
- `find_amenities_near_address` - Raw amenity list with quality data
|
||||||
|
|
||||||
## 🎯 **Data Coverage**
|
## 🎯 **Data Coverage**
|
||||||
|
|
||||||
The data covers recent Israeli real estate transactions and can help with:
|
The data covers recent Israeli real estate transactions and can help with:
|
||||||
- Property research and valuation
|
- Property research and valuation ✅
|
||||||
- Investment analysis and decision-making
|
- Investment analysis and decision-making ✅ (🚧 advanced metrics in progress)
|
||||||
- Market understanding and trends
|
- Market understanding and trends ✅
|
||||||
- Comparative analysis across locations
|
- Comparative analysis across locations ✅
|
||||||
- Due diligence for real estate decisions
|
- Due diligence for real estate decisions ✅
|
||||||
- Amenity-based location scoring and quality of life analysis
|
- Amenity-based location scoring and quality of life analysis 📋 (planned for future release)
|
||||||
|
|
||||||
## 🚀 **Getting Started**
|
## 🚀 **Getting Started**
|
||||||
|
|
||||||
Simply connect to the nadlan-mcp server through your MCP client (like Cursor) and start asking questions about Israeli real estate data. The server supports both Hebrew and English addresses and provides comprehensive market analysis capabilities.
|
Simply connect to the nadlan-mcp server through your MCP client (like Cursor) and start asking questions about Israeli real estate data. The server supports both Hebrew and English addresses and provides comprehensive market analysis capabilities.
|
||||||
|
|
||||||
|
## 🔄 **Roadmap & Timeline**
|
||||||
|
|
||||||
|
### Phase 1: Core Reliability & Quality (Current)
|
||||||
|
- ✅ Configuration management system
|
||||||
|
- ✅ Retry logic with exponential backoff
|
||||||
|
- ✅ Rate limiting protection
|
||||||
|
- ✅ Input validation and error handling
|
||||||
|
- 🚧 Enhanced deal filtering
|
||||||
|
- 🚧 Valuation data provision tools
|
||||||
|
- 🚧 Market activity metrics
|
||||||
|
|
||||||
|
### Phase 2: Architecture Improvements (Next)
|
||||||
|
- Data models with Pydantic
|
||||||
|
- Separation of concerns (API client, analyzers, tools)
|
||||||
|
- LLM-friendly tool design with summarized_response parameter
|
||||||
|
- Comprehensive testing suite
|
||||||
|
|
||||||
|
### Phase 3: Documentation & Developer Experience
|
||||||
|
- Architecture documentation
|
||||||
|
- API reference guide
|
||||||
|
- Usage examples
|
||||||
|
- Contributing guidelines
|
||||||
|
|
||||||
|
### Phase 4: Future Features
|
||||||
|
- Amenity scoring with quality metrics
|
||||||
|
- In-memory caching
|
||||||
|
- Async/parallel processing
|
||||||
|
- Production-ready caching (Redis)
|
||||||
|
- Multi-language support
|
||||||
|
- Database integration
|
||||||
|
|
||||||
|
## 📞 **Support & Contributions**
|
||||||
|
|
||||||
|
For issues, questions, or contributions, please create an issue in the repository. The project follows semantic versioning and maintains backward compatibility.
|
||||||
|
|||||||
@@ -1,311 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Simple Israel Real Estate MCP Server
|
|
||||||
|
|
||||||
A simplified MCP server that provides access to Israeli real estate data.
|
|
||||||
Focuses on the main use cases for AI agents.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from typing import Any, Dict, List
|
|
||||||
|
|
||||||
# Simple MCP server implementation
|
|
||||||
class SimpleMCPServer:
|
|
||||||
def __init__(self):
|
|
||||||
from nadlan_mcp.govmap import GovmapClient
|
|
||||||
self.client = GovmapClient()
|
|
||||||
self.tools = {
|
|
||||||
"find_recent_deals_for_address": {
|
|
||||||
"description": "🏠 Find all recent real estate deals for a given address. Main function for comprehensive market analysis.",
|
|
||||||
"parameters": {
|
|
||||||
"address": "Full address to search for (Hebrew or English)",
|
|
||||||
"years_back": "How many years back to search (default: 2)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"analyze_market_trends": {
|
|
||||||
"description": "📊 Analyze market trends for a specific address with price analysis and insights",
|
|
||||||
"parameters": {
|
|
||||||
"address": "Address to analyze",
|
|
||||||
"years_back": "Years of data to analyze (default: 3)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compare_neighborhoods": {
|
|
||||||
"description": "🏘️ Compare real estate market data between multiple addresses",
|
|
||||||
"parameters": {
|
|
||||||
"addresses": "List of addresses to compare (2-5 addresses)",
|
|
||||||
"years_back": "Years of data to compare (default: 2)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autocomplete_address": {
|
|
||||||
"description": "🔍 Search for Israeli addresses using autocomplete",
|
|
||||||
"parameters": {
|
|
||||||
"search_text": "Address to search for"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def list_tools(self) -> str:
|
|
||||||
"""List available tools."""
|
|
||||||
tools_list = ["📋 Available Real Estate Tools:"]
|
|
||||||
for name, info in self.tools.items():
|
|
||||||
tools_list.append(f"\n🔧 {name}")
|
|
||||||
tools_list.append(f" {info['description']}")
|
|
||||||
tools_list.append(" Parameters:")
|
|
||||||
for param, desc in info['parameters'].items():
|
|
||||||
tools_list.append(f" • {param}: {desc}")
|
|
||||||
|
|
||||||
return "\n".join(tools_list)
|
|
||||||
|
|
||||||
def call_tool(self, tool_name: str, **kwargs) -> str:
|
|
||||||
"""Call a specific tool."""
|
|
||||||
try:
|
|
||||||
if tool_name == "find_recent_deals_for_address":
|
|
||||||
return self._find_recent_deals(**kwargs)
|
|
||||||
elif tool_name == "analyze_market_trends":
|
|
||||||
return self._analyze_trends(**kwargs)
|
|
||||||
elif tool_name == "compare_neighborhoods":
|
|
||||||
return self._compare_neighborhoods(**kwargs)
|
|
||||||
elif tool_name == "autocomplete_address":
|
|
||||||
return self._autocomplete_address(**kwargs)
|
|
||||||
else:
|
|
||||||
return f"❌ Unknown tool: {tool_name}"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error in {tool_name}: {str(e)}"
|
|
||||||
|
|
||||||
def _find_recent_deals(self, address: str, years_back: int = 2) -> str:
|
|
||||||
"""Find recent deals for an address."""
|
|
||||||
deals = self.client.find_recent_deals_for_address(address, years_back)
|
|
||||||
|
|
||||||
if not deals:
|
|
||||||
return f"No recent deals found for: {address}"
|
|
||||||
|
|
||||||
# Calculate statistics
|
|
||||||
amounts = [deal.get('dealAmount') for deal in deals if isinstance(deal.get('dealAmount'), (int, float))]
|
|
||||||
areas = [deal.get('assetArea') for deal in deals if isinstance(deal.get('assetArea'), (int, float))]
|
|
||||||
|
|
||||||
result = [f"🏠 REAL ESTATE ANALYSIS FOR: {address}"]
|
|
||||||
result.append(f"📊 Total deals found: {len(deals)}")
|
|
||||||
|
|
||||||
if amounts:
|
|
||||||
avg_price = sum(amounts) / len(amounts)
|
|
||||||
result.append(f"💰 Average price: {avg_price:,.0f} NIS")
|
|
||||||
result.append(f"📈 Price range: {min(amounts):,} - {max(amounts):,} NIS")
|
|
||||||
|
|
||||||
if areas:
|
|
||||||
avg_area = sum(areas) / len(areas)
|
|
||||||
result.append(f"📏 Average area: {avg_area:.0f} m²")
|
|
||||||
|
|
||||||
result.append(f"\n🏡 Recent deals (last {years_back} years):")
|
|
||||||
|
|
||||||
# Show first 10 deals
|
|
||||||
for i, deal in enumerate(deals[:10], 1):
|
|
||||||
price = deal.get('dealAmount', 'N/A')
|
|
||||||
area = deal.get('assetArea', 'N/A')
|
|
||||||
date = deal.get('dealDate', 'N/A')[:10] if deal.get('dealDate') else 'N/A'
|
|
||||||
prop_type = deal.get('propertyTypeDescription', 'N/A')
|
|
||||||
neighborhood = deal.get('neighborhood', 'N/A')
|
|
||||||
|
|
||||||
if isinstance(price, (int, float)):
|
|
||||||
result.append(f"{i}. {date} | {price:,} NIS | {area} m² | {prop_type} | {neighborhood}")
|
|
||||||
else:
|
|
||||||
result.append(f"{i}. {date} | {price} | {area} m² | {prop_type} | {neighborhood}")
|
|
||||||
|
|
||||||
if len(deals) > 10:
|
|
||||||
result.append(f"\n... and {len(deals) - 10} more deals")
|
|
||||||
|
|
||||||
return "\n".join(result)
|
|
||||||
|
|
||||||
def _analyze_trends(self, address: str, years_back: int = 3) -> str:
|
|
||||||
"""Analyze market trends."""
|
|
||||||
deals = self.client.find_recent_deals_for_address(address, years_back)
|
|
||||||
|
|
||||||
if not deals:
|
|
||||||
return f"No market data found for {address}"
|
|
||||||
|
|
||||||
# Analyze trends by year
|
|
||||||
yearly_data = {}
|
|
||||||
property_types = {}
|
|
||||||
neighborhoods = set()
|
|
||||||
|
|
||||||
for deal in deals:
|
|
||||||
date_str = deal.get('dealDate', '')
|
|
||||||
if date_str:
|
|
||||||
year = date_str[:4]
|
|
||||||
price = deal.get('dealAmount')
|
|
||||||
area = deal.get('assetArea')
|
|
||||||
prop_type = deal.get('propertyTypeDescription', 'Unknown')
|
|
||||||
neighborhood = deal.get('neighborhood')
|
|
||||||
|
|
||||||
if neighborhood:
|
|
||||||
neighborhoods.add(neighborhood)
|
|
||||||
|
|
||||||
if isinstance(price, (int, float)) and isinstance(area, (int, float)) and area > 0:
|
|
||||||
if year not in yearly_data:
|
|
||||||
yearly_data[year] = []
|
|
||||||
yearly_data[year].append({
|
|
||||||
'price': price,
|
|
||||||
'area': area,
|
|
||||||
'price_per_sqm': price / area
|
|
||||||
})
|
|
||||||
|
|
||||||
property_types[prop_type] = property_types.get(prop_type, 0) + 1
|
|
||||||
|
|
||||||
# Generate analysis
|
|
||||||
analysis = [f"📊 MARKET TRENDS ANALYSIS: {address}"]
|
|
||||||
analysis.append(f"📅 Analysis period: Last {years_back} years")
|
|
||||||
analysis.append(f"🏘️ Neighborhoods: {', '.join(neighborhoods) if neighborhoods else 'N/A'}")
|
|
||||||
analysis.append(f"🏠 Property types: {', '.join([f'{k} ({v})' for k, v in property_types.items()])}")
|
|
||||||
|
|
||||||
if yearly_data:
|
|
||||||
analysis.append(f"\n📈 YEARLY TRENDS:")
|
|
||||||
|
|
||||||
for year in sorted(yearly_data.keys(), reverse=True):
|
|
||||||
year_deals = yearly_data[year]
|
|
||||||
avg_price = sum(d['price'] for d in year_deals) / len(year_deals)
|
|
||||||
avg_area = sum(d['area'] for d in year_deals) / len(year_deals)
|
|
||||||
avg_price_per_sqm = sum(d['price_per_sqm'] for d in year_deals) / len(year_deals)
|
|
||||||
|
|
||||||
analysis.append(f" {year}: {len(year_deals)} deals | Avg: {avg_price:,.0f} NIS | {avg_area:.0f} m² | {avg_price_per_sqm:,.0f} NIS/m²")
|
|
||||||
|
|
||||||
# Price trend
|
|
||||||
years_sorted = sorted(yearly_data.keys())
|
|
||||||
if len(years_sorted) >= 2:
|
|
||||||
first_year_avg = sum(d['price_per_sqm'] for d in yearly_data[years_sorted[0]]) / len(yearly_data[years_sorted[0]])
|
|
||||||
last_year_avg = sum(d['price_per_sqm'] for d in yearly_data[years_sorted[-1]]) / len(yearly_data[years_sorted[-1]])
|
|
||||||
|
|
||||||
trend = ((last_year_avg - first_year_avg) / first_year_avg) * 100
|
|
||||||
trend_direction = "📈 Rising" if trend > 0 else "📉 Declining" if trend < 0 else "➡️ Stable"
|
|
||||||
analysis.append(f"\n🎯 Price Trend: {trend_direction} ({trend:+.1f}% over period)")
|
|
||||||
|
|
||||||
return "\n".join(analysis)
|
|
||||||
|
|
||||||
def _compare_neighborhoods(self, addresses: List[str], years_back: int = 2) -> str:
|
|
||||||
"""Compare neighborhoods."""
|
|
||||||
if len(addresses) < 2:
|
|
||||||
return "❌ At least 2 addresses are required for comparison"
|
|
||||||
|
|
||||||
comparison = [f"🏘️ NEIGHBORHOOD COMPARISON"]
|
|
||||||
comparison.append(f"📅 Comparing last {years_back} years of data\n")
|
|
||||||
|
|
||||||
address_data = {}
|
|
||||||
|
|
||||||
for address in addresses:
|
|
||||||
deals = self.client.find_recent_deals_for_address(address, years_back)
|
|
||||||
|
|
||||||
if deals:
|
|
||||||
amounts = [deal.get('dealAmount') for deal in deals if isinstance(deal.get('dealAmount'), (int, float))]
|
|
||||||
areas = [deal.get('assetArea') for deal in deals if isinstance(deal.get('assetArea'), (int, float))]
|
|
||||||
neighborhoods = {deal.get('neighborhood') for deal in deals if deal.get('neighborhood')}
|
|
||||||
|
|
||||||
if amounts and areas:
|
|
||||||
price_per_sqm = [amounts[i] / areas[i] for i in range(min(len(amounts), len(areas))) if areas[i] > 0]
|
|
||||||
|
|
||||||
address_data[address] = {
|
|
||||||
'deals_count': len(deals),
|
|
||||||
'avg_price': sum(amounts) / len(amounts),
|
|
||||||
'avg_area': sum(areas) / len(areas),
|
|
||||||
'avg_price_per_sqm': sum(price_per_sqm) / len(price_per_sqm) if price_per_sqm else 0,
|
|
||||||
'neighborhoods': neighborhoods
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate comparison
|
|
||||||
for address, data in address_data.items():
|
|
||||||
comparison.append(f"📍 {address}:")
|
|
||||||
comparison.append(f" • {data['deals_count']} deals found")
|
|
||||||
comparison.append(f" • Avg price: {data['avg_price']:,.0f} NIS")
|
|
||||||
comparison.append(f" • Avg area: {data['avg_area']:.0f} m²")
|
|
||||||
comparison.append(f" • Price per m²: {data['avg_price_per_sqm']:,.0f} NIS")
|
|
||||||
comparison.append(f" • Neighborhoods: {', '.join(data['neighborhoods'])}")
|
|
||||||
comparison.append("")
|
|
||||||
|
|
||||||
# Ranking
|
|
||||||
if address_data:
|
|
||||||
comparison.append("🏆 RANKINGS:")
|
|
||||||
by_price_per_sqm = sorted(address_data.items(), key=lambda x: x[1]['avg_price_per_sqm'], reverse=True)
|
|
||||||
|
|
||||||
comparison.append("💰 Most expensive (NIS/m²):")
|
|
||||||
for i, (addr, data) in enumerate(by_price_per_sqm, 1):
|
|
||||||
comparison.append(f" {i}. {addr}: {data['avg_price_per_sqm']:,.0f} NIS/m²")
|
|
||||||
|
|
||||||
return "\n".join(comparison)
|
|
||||||
|
|
||||||
def _autocomplete_address(self, search_text: str) -> str:
|
|
||||||
"""Autocomplete address search."""
|
|
||||||
result = self.client.autocomplete_address(search_text)
|
|
||||||
|
|
||||||
formatted_results = []
|
|
||||||
for item in result.get("results", []):
|
|
||||||
formatted_results.append(f"• {item.get('text')} (type: {item.get('type')}, score: {item.get('score')})")
|
|
||||||
|
|
||||||
return f"Found {result.get('resultsCount', 0)} address matches:\n" + "\n".join(formatted_results[:5])
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Interactive demo of the MCP server functionality."""
|
|
||||||
print("🏠 Israel Real Estate MCP Server - Demo Mode")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
server = SimpleMCPServer()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
print("\n" + "=" * 50)
|
|
||||||
print("🔧 Available commands:")
|
|
||||||
print("1. list - List all available tools")
|
|
||||||
print("2. find - Find recent deals for address")
|
|
||||||
print("3. trends - Analyze market trends")
|
|
||||||
print("4. compare - Compare neighborhoods")
|
|
||||||
print("5. search - Search for addresses")
|
|
||||||
print("6. quit - Exit")
|
|
||||||
|
|
||||||
choice = input("\nSelect a command (1-6): ").strip()
|
|
||||||
|
|
||||||
if choice == "1":
|
|
||||||
print(server.list_tools())
|
|
||||||
|
|
||||||
elif choice == "2":
|
|
||||||
address = input("Enter address: ").strip()
|
|
||||||
years = input("Years back (default 2): ").strip()
|
|
||||||
years_back = int(years) if years.isdigit() else 2
|
|
||||||
|
|
||||||
print("\n🔍 Searching for deals...")
|
|
||||||
result = server.call_tool("find_recent_deals_for_address", address=address, years_back=years_back)
|
|
||||||
print(result)
|
|
||||||
|
|
||||||
elif choice == "3":
|
|
||||||
address = input("Enter address: ").strip()
|
|
||||||
years = input("Years back (default 3): ").strip()
|
|
||||||
years_back = int(years) if years.isdigit() else 3
|
|
||||||
|
|
||||||
print("\n📊 Analyzing trends...")
|
|
||||||
result = server.call_tool("analyze_market_trends", address=address, years_back=years_back)
|
|
||||||
print(result)
|
|
||||||
|
|
||||||
elif choice == "4":
|
|
||||||
addresses_input = input("Enter addresses (comma separated): ").strip()
|
|
||||||
addresses = [addr.strip() for addr in addresses_input.split(",")]
|
|
||||||
years = input("Years back (default 2): ").strip()
|
|
||||||
years_back = int(years) if years.isdigit() else 2
|
|
||||||
|
|
||||||
print("\n🏘️ Comparing neighborhoods...")
|
|
||||||
result = server.call_tool("compare_neighborhoods", addresses=addresses, years_back=years_back)
|
|
||||||
print(result)
|
|
||||||
|
|
||||||
elif choice == "5":
|
|
||||||
search_text = input("Enter search text: ").strip()
|
|
||||||
|
|
||||||
print("\n🔍 Searching addresses...")
|
|
||||||
result = server.call_tool("autocomplete_address", search_text=search_text)
|
|
||||||
print(result)
|
|
||||||
|
|
||||||
elif choice == "6":
|
|
||||||
print("👋 Goodbye!")
|
|
||||||
break
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("❌ Invalid choice. Please select 1-6.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
"""
|
||||||
|
Configuration management for Nadlan MCP.
|
||||||
|
|
||||||
|
This module provides centralized configuration for API clients, timeouts,
|
||||||
|
rate limiting, and other settings. Configuration can be set via environment
|
||||||
|
variables or code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GovmapConfig:
|
||||||
|
"""Configuration for Govmap API client."""
|
||||||
|
|
||||||
|
# API settings
|
||||||
|
base_url: str = field(
|
||||||
|
default_factory=lambda: os.getenv(
|
||||||
|
"GOVMAP_BASE_URL",
|
||||||
|
"https://www.govmap.gov.il/api/"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Timeout settings (in seconds)
|
||||||
|
connect_timeout: int = field(
|
||||||
|
default_factory=lambda: int(os.getenv("GOVMAP_CONNECT_TIMEOUT", "10"))
|
||||||
|
)
|
||||||
|
read_timeout: int = field(
|
||||||
|
default_factory=lambda: int(os.getenv("GOVMAP_READ_TIMEOUT", "30"))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Retry settings
|
||||||
|
max_retries: int = field(
|
||||||
|
default_factory=lambda: int(os.getenv("GOVMAP_MAX_RETRIES", "3"))
|
||||||
|
)
|
||||||
|
retry_min_wait: int = field(
|
||||||
|
default_factory=lambda: int(os.getenv("GOVMAP_RETRY_MIN_WAIT", "1"))
|
||||||
|
)
|
||||||
|
retry_max_wait: int = field(
|
||||||
|
default_factory=lambda: int(os.getenv("GOVMAP_RETRY_MAX_WAIT", "10"))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
requests_per_second: float = field(
|
||||||
|
default_factory=lambda: float(os.getenv("GOVMAP_REQUESTS_PER_SECOND", "5.0"))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Default search parameters
|
||||||
|
default_radius_meters: int = field(
|
||||||
|
default_factory=lambda: int(os.getenv("GOVMAP_DEFAULT_RADIUS", "50"))
|
||||||
|
)
|
||||||
|
default_years_back: int = field(
|
||||||
|
default_factory=lambda: int(os.getenv("GOVMAP_DEFAULT_YEARS_BACK", "2"))
|
||||||
|
)
|
||||||
|
default_deal_limit: int = field(
|
||||||
|
default_factory=lambda: int(os.getenv("GOVMAP_DEFAULT_DEAL_LIMIT", "100"))
|
||||||
|
)
|
||||||
|
|
||||||
|
# User agent
|
||||||
|
user_agent: str = field(
|
||||||
|
default_factory=lambda: os.getenv(
|
||||||
|
"GOVMAP_USER_AGENT",
|
||||||
|
"NadlanMCP/1.0.0"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
"""Validate configuration after initialization."""
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Validate configuration values."""
|
||||||
|
if self.connect_timeout <= 0:
|
||||||
|
raise ValueError("connect_timeout must be positive")
|
||||||
|
if self.read_timeout <= 0:
|
||||||
|
raise ValueError("read_timeout must be positive")
|
||||||
|
if self.max_retries < 0:
|
||||||
|
raise ValueError("max_retries must be non-negative")
|
||||||
|
if self.retry_min_wait <= 0:
|
||||||
|
raise ValueError("retry_min_wait must be positive")
|
||||||
|
if self.retry_max_wait < self.retry_min_wait:
|
||||||
|
raise ValueError("retry_max_wait must be >= retry_min_wait")
|
||||||
|
if self.requests_per_second <= 0:
|
||||||
|
raise ValueError("requests_per_second must be positive")
|
||||||
|
if self.default_radius_meters <= 0:
|
||||||
|
raise ValueError("default_radius_meters must be positive")
|
||||||
|
if self.default_years_back <= 0:
|
||||||
|
raise ValueError("default_years_back must be positive")
|
||||||
|
if self.default_deal_limit <= 0:
|
||||||
|
raise ValueError("default_deal_limit must be positive")
|
||||||
|
if not self.base_url:
|
||||||
|
raise ValueError("base_url cannot be empty")
|
||||||
|
if not self.user_agent:
|
||||||
|
raise ValueError("user_agent cannot be empty")
|
||||||
|
|
||||||
|
|
||||||
|
# Global configuration instance
|
||||||
|
_config: Optional[GovmapConfig] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_config() -> GovmapConfig:
|
||||||
|
"""
|
||||||
|
Get the global configuration instance.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GovmapConfig: The global configuration object
|
||||||
|
"""
|
||||||
|
global _config
|
||||||
|
if _config is None:
|
||||||
|
_config = GovmapConfig()
|
||||||
|
return _config
|
||||||
|
|
||||||
|
|
||||||
|
def set_config(config: GovmapConfig):
|
||||||
|
"""
|
||||||
|
Set the global configuration instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: The new configuration object
|
||||||
|
"""
|
||||||
|
global _config
|
||||||
|
_config = config
|
||||||
|
|
||||||
|
|
||||||
|
def reset_config():
|
||||||
|
"""Reset the global configuration to default values."""
|
||||||
|
global _config
|
||||||
|
_config = None
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ using the FastMCP library with simplified, working functions.
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict
|
from typing import List, Dict, Optional
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
from nadlan_mcp.govmap import GovmapClient
|
from nadlan_mcp.govmap import GovmapClient
|
||||||
|
|
||||||
@@ -532,6 +532,152 @@ def compare_addresses(addresses: List[str]) -> str:
|
|||||||
logger.error(f"Error in compare_addresses: {e}")
|
logger.error(f"Error in compare_addresses: {e}")
|
||||||
return f"Error comparing addresses: {str(e)}"
|
return f"Error comparing addresses: {str(e)}"
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_valuation_comparables(
|
||||||
|
address: str,
|
||||||
|
years_back: int = 2,
|
||||||
|
property_type: Optional[str] = None,
|
||||||
|
min_rooms: Optional[float] = None,
|
||||||
|
max_rooms: Optional[float] = None,
|
||||||
|
min_price: Optional[float] = None,
|
||||||
|
max_price: Optional[float] = None,
|
||||||
|
min_area: Optional[float] = None,
|
||||||
|
max_area: Optional[float] = None,
|
||||||
|
min_floor: Optional[int] = None,
|
||||||
|
max_floor: Optional[int] = None
|
||||||
|
) -> str:
|
||||||
|
"""Get comparable properties for valuation analysis.
|
||||||
|
|
||||||
|
This tool provides detailed comparable deals filtered by your criteria.
|
||||||
|
The LLM can then analyze these comparables and estimate property values.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
address: The address to find comparables for (in Hebrew or English)
|
||||||
|
years_back: How many years back to search (default: 2)
|
||||||
|
property_type: Filter by property type (e.g., "דירה", "בית", "פנטהאוז")
|
||||||
|
min_rooms: Minimum number of rooms
|
||||||
|
max_rooms: Maximum number of rooms
|
||||||
|
min_price: Minimum deal amount (NIS)
|
||||||
|
max_price: Maximum deal amount (NIS)
|
||||||
|
min_area: Minimum asset area (square meters)
|
||||||
|
max_area: Maximum asset area (square meters)
|
||||||
|
min_floor: Minimum floor number
|
||||||
|
max_floor: Maximum floor number
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON string containing filtered comparable deals with full details
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get all deals for the address
|
||||||
|
deals = client.find_recent_deals_for_address(address, years_back)
|
||||||
|
|
||||||
|
if not deals:
|
||||||
|
return json.dumps({
|
||||||
|
"address": address,
|
||||||
|
"years_back": years_back,
|
||||||
|
"comparables": [],
|
||||||
|
"message": "No deals found for this address"
|
||||||
|
}, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# Apply filters
|
||||||
|
filtered_deals = client.filter_deals_by_criteria(
|
||||||
|
deals,
|
||||||
|
property_type=property_type,
|
||||||
|
min_rooms=min_rooms,
|
||||||
|
max_rooms=max_rooms,
|
||||||
|
min_price=min_price,
|
||||||
|
max_price=max_price,
|
||||||
|
min_area=min_area,
|
||||||
|
max_area=max_area,
|
||||||
|
min_floor=min_floor,
|
||||||
|
max_floor=max_floor
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate statistics on filtered comparables
|
||||||
|
stats = client.calculate_deal_statistics(filtered_deals)
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"address": address,
|
||||||
|
"years_back": years_back,
|
||||||
|
"filters_applied": {
|
||||||
|
"property_type": property_type,
|
||||||
|
"rooms": f"{min_rooms}-{max_rooms}" if min_rooms or max_rooms else None,
|
||||||
|
"price": f"{min_price}-{max_price}" if min_price or max_price else None,
|
||||||
|
"area": f"{min_area}-{max_area}" if min_area or max_area else None,
|
||||||
|
"floor": f"{min_floor}-{max_floor}" if min_floor or max_floor else None,
|
||||||
|
},
|
||||||
|
"total_comparables": len(filtered_deals),
|
||||||
|
"statistics": stats,
|
||||||
|
"comparables": filtered_deals
|
||||||
|
}, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in get_valuation_comparables: {e}")
|
||||||
|
return f"Error getting valuation comparables: {str(e)}"
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_deal_statistics(
|
||||||
|
address: str,
|
||||||
|
years_back: int = 2,
|
||||||
|
property_type: Optional[str] = None,
|
||||||
|
min_rooms: Optional[float] = None,
|
||||||
|
max_rooms: Optional[float] = None
|
||||||
|
) -> str:
|
||||||
|
"""Calculate statistical aggregations on deal data for an address.
|
||||||
|
|
||||||
|
This tool provides quick statistical summaries without returning all raw deals.
|
||||||
|
Useful when LLM needs calculations on large datasets without full details.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
address: The address to analyze (in Hebrew or English)
|
||||||
|
years_back: How many years back to analyze (default: 2)
|
||||||
|
property_type: Filter by property type (e.g., "דירה", "בית")
|
||||||
|
min_rooms: Minimum number of rooms
|
||||||
|
max_rooms: Maximum number of rooms
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON string containing statistical metrics (mean, median, percentiles, etc.)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get all deals for the address
|
||||||
|
deals = client.find_recent_deals_for_address(address, years_back)
|
||||||
|
|
||||||
|
if not deals:
|
||||||
|
return json.dumps({
|
||||||
|
"address": address,
|
||||||
|
"years_back": years_back,
|
||||||
|
"statistics": {
|
||||||
|
"count": 0,
|
||||||
|
"message": "No deals found for this address"
|
||||||
|
}
|
||||||
|
}, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# Apply filters if provided
|
||||||
|
if property_type or min_rooms or max_rooms:
|
||||||
|
deals = client.filter_deals_by_criteria(
|
||||||
|
deals,
|
||||||
|
property_type=property_type,
|
||||||
|
min_rooms=min_rooms,
|
||||||
|
max_rooms=max_rooms
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
|
stats = client.calculate_deal_statistics(deals)
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"address": address,
|
||||||
|
"years_back": years_back,
|
||||||
|
"filters_applied": {
|
||||||
|
"property_type": property_type,
|
||||||
|
"rooms": f"{min_rooms}-{max_rooms}" if min_rooms or max_rooms else None,
|
||||||
|
},
|
||||||
|
"statistics": stats
|
||||||
|
}, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in get_deal_statistics: {e}")
|
||||||
|
return f"Error calculating deal statistics: {str(e)}"
|
||||||
|
|
||||||
# Run the server
|
# Run the server
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
mcp.run()
|
mcp.run()
|
||||||
+691
-122
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
|||||||
|
# Development dependencies
|
||||||
|
-r requirements.txt
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
pytest>=7.0.0,<8.0.0
|
||||||
|
pytest-cov>=4.0.0,<5.0.0
|
||||||
|
pytest-mock>=3.10.0,<4.0.0
|
||||||
|
vcrpy>=4.2.0,<5.0.0
|
||||||
|
|
||||||
|
# Code quality
|
||||||
|
black>=23.0.0,<24.0.0
|
||||||
|
isort>=5.12.0,<6.0.0
|
||||||
|
flake8>=6.0.0,<7.0.0
|
||||||
|
mypy>=1.0.0,<2.0.0
|
||||||
|
pre-commit>=3.0.0,<4.0.0
|
||||||
|
|
||||||
|
# Type stubs
|
||||||
|
types-requests>=2.31.0,<3.0.0
|
||||||
|
|
||||||
+5
-6
@@ -1,6 +1,5 @@
|
|||||||
requests>=2.31.0
|
requests>=2.31.0,<3.0.0
|
||||||
python-dotenv>=1.0.0
|
python-dotenv>=1.0.0,<2.0.0
|
||||||
pytest>=7.0.0
|
mcp>=1.0.0,<2.0.0
|
||||||
mcp>=1.0.0
|
fastmcp>=0.1.0,<1.0.0
|
||||||
fastmcp>=0.1.0
|
pydantic>=2.0.0,<3.0.0
|
||||||
types-requests>=2.31.0
|
|
||||||
|
|||||||
Reference in New Issue
Block a user