Commit Graph

31 Commits

Author SHA1 Message Date
Nitzan Pomerantz b87cb268d5 Improve: MCP efficiency & fix radius filtering
- Remove bloat fields (shape, objectid, etc), add sequential IDs
- Add lang param (he/en) for Hebrew/English text values
- Reduce JSON whitespace (indent=None)
- Fix distance_meters: extract centroid from WKT shape geometry
- Add search_coordinates to all address-based tool responses
- Fix radius filtering: properly filter deals beyond radius_meters
- Change default radius from 30m to 50m
- Fix get_deal_statistics: return NO deals (stats only)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 09:34:42 +02:00
Nitzan Pomerantz 88ae481e52 Add: Config for tool enable/disable, BETA warnings
Config system:
- 10 env vars to enable/disable each tool
- Disabled by default: get_deals_by_radius, get_street_deals, get_market_activity_metrics
- Enabled by default: all other tools

BETA warnings:
- analyze_market_trends: added BETA notice
- compare_addresses: added BETA notice

Tests:
- 17 tests skipped when tools disabled
- All 311 tests pass

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 22:37:55 +02:00
Nitzan Pomerantz 5daaf70021 Fix: List index bug in outlier detection, add error logging
Bug #1: Fixed IndexError in outlier_detection.py:256
- Missing enumerate() caused stale loop var to access beyond bounds
- Occurred when hard bounds filtered deals before IQR processing
- Added test reproducing exact scenario (21 deals → 8 filtered)

Bug #2: Added stack traces to all MCP tool error logs
- Added exc_info=True to 10 MCP tools' error handlers
- Improves debugging by logging full stack traces

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 19:56:15 +02:00
Nitzan P bd0932abf6 Fix flaky temporal test for market activity trend detection
The test_market_activity_trend_stable test was failing intermittently
because it used get_recent_date() which approximates months as 30 days,
causing inconsistent month boundary calculations depending on when the
test runs.

Root cause:
- Using months_ago * 30 creates shifting month boundaries
- Calendar dates don't align consistently with 30-day periods
- Deals could fall into unexpected months based on test execution date
- Trend calculation compares first half vs second half of months
- Inconsistent month grouping led to "decreasing" instead of "stable"

Solution:
- Use explicit, deterministic dates (15th of each month)
- Calculate year-month combinations going back from current month
- Ensures exactly 1 deal per month for 12 consecutive months
- All deals guaranteed to be within time_period_months=12 window
- Month boundaries are now consistent regardless of test execution date

Before: Test result varied based on current date (flaky)
After: Test result is always "stable" (deterministic)

This ensures the test validates the trend detection logic without
temporal flakiness.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 23:56:31 +02:00
Nitzan P 6882d7ab71 Add outlier filtering metadata to get_valuation_comparables response
The MCP now includes detailed outlier filtering information in the response,
allowing LLMs and bots to inform users about data quality improvements.

Changes to nadlan_mcp/fastmcp_server.py:
- Updated get_valuation_comparables to include outlier filtering metadata
- deal_breakdown now includes when filtering is applied:
  - total_deals: Count after filtering (final result)
  - total_deals_before_filtering: Original count before filtering
  - outliers_removed: Number of deals filtered out
  - filtering_method: Method used ("iqr", "percent", or "none")
  - iqr_multiplier: IQR multiplier when using IQR method (e.g., 1.5)
- Updated docstring to clarify that outlier filtering is automatic
  and metadata is included in response

Changes to tests/e2e/test_mcp_tools_comprehensive.py:
- Added assertions to verify outlier filtering metadata fields
- Test now validates the structure and types of filtering information

Example response:
{
  "market_statistics": {
    "deal_breakdown": {
      "total_deals": 15,
      "total_deals_before_filtering": 17,
      "outliers_removed": 2,
      "filtering_method": "iqr",
      "iqr_multiplier": 1.5
    }
  }
}

This allows bots like nadlan-bot to properly inform users:
"Found 17 comparable deals, filtered 2 outliers using IQR method (k=1.5),
 showing 15 deals."

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 23:45:56 +02:00
Nitzan P e791d3c1ed 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>
2025-11-23 09:49:52 +02:00
Nitzan P b78346f3b0 Add statistical refinement and outlier detection system
Implement configurable outlier detection and robust statistical measures to
improve analysis accuracy for real estate data. Addresses issues with data
entry errors, partial deals, and other anomalies that skew statistics.

Key Features:
- IQR-based outlier detection (moderate filtering by default, k=1.5)
- Hard bounds filtering for obvious errors (price_per_sqm, deal_amount)
- Robust volatility using IQR instead of std_dev for investment analysis
- Transparent reporting with both filtered and unfiltered statistics

Implementation:
- Add outlier_detection.py module with IQR/percent/hard bounds methods
- Add OutlierReport model and enhance DealStatistics with filtered fields
- Update calculate_deal_statistics() to support optional outlier filtering
- Update analyze_investment_potential() to use robust volatility
- Add 9 new configuration parameters for customization
- Add comprehensive test suite (24 tests) for outlier detection
- Update CLAUDE.md with usage documentation

Configuration (all via env vars):
- ANALYSIS_OUTLIER_METHOD=iqr (default, or percent/none)
- ANALYSIS_IQR_MULTIPLIER=1.5 (moderate, 3.0=conservative)
- ANALYSIS_PRICE_PER_SQM_MIN/MAX=1000/100000 (bounds in NIS/sqm)
- ANALYSIS_MIN_DEAL_AMOUNT=100000 (catches partial deals)
- ANALYSIS_USE_ROBUST_VOLATILITY=true (IQR-based CV)
- ANALYSIS_USE_ROBUST_TRENDS=true (filter before regression)

Testing:
- All existing tests pass (326 passed)
- 24 new comprehensive outlier detection tests
- Real-world scenario tests (partial deals, data errors)

Backward Compatible:
- Default behavior improves accuracy without breaking changes
- All new fields in models are optional
- Config parameters have sensible defaults

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 23:45:41 +02:00
Nitzan P 5be68a5b04 Modernize to pyproject.toml and fix test assertion
- Remove requirements.txt and requirements-dev.txt (duplicates pyproject.toml)
- Update Dockerfile to use 'pip install .' instead of requirements.txt
- Update README.md and GitHub workflows to use 'pip install -e .[dev]'
- Fix test_get_valuation_comparables: check for 'deal_date' instead of non-existent 'asset_room_num'
  (rooms field is optional and excluded when None due to exclude_none=True)
- Fix fastmcp version constraint in pyproject.toml (>=2.13.0,<3.0.0)
- Add vcrpy to dev dependencies

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 23:00:03 +02:00
Nitzan Pomerantz fd79ea88bf Fix test_parse_deal_dates_basic temporal assertion
Adjusted sample_deals fixture to ensure 5 deals across 5 distinct months.
Previous dates collapsed into 3 months due to naive 30-day calculations.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 00:08:42 +02:00
Nitzan Pomerantz 02e69b6d3b Ran ruff format etc. 2025-10-31 19:02:33 +02:00
Nitzan Pomerantz 739d4f8578 Examples and code quality 2025-10-31 00:31:14 +02:00
Nitzan Pomerantz e4aa6487ff Ruff fixes 2025-10-30 22:24:40 +02:00
Nitzan Pomerantz 6632ac1669 Fix failing tests again 2025-10-30 20:54:18 +02:00
Nitzan Pomerantz 67a17b022d Remove irrelevant code as per gemini cr 2025-10-30 20:25:14 +02:00
Nitzan Pomerantz bdfc5e3d3e Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-10-30 20:18:09 +02:00
Nitzan Pomerantz 04a1e68604 Phase 5 2025-10-30 18:26:48 +02:00
Nitzan Pomerantz a234e809bf Testing update 2025-10-27 23:33:09 +02:00
Nitzan Pomerantz 247ddad8cd CRITICAL FIX: get_deals_by_radius returns polygon metadata, not deals
## Root Cause
After Pydantic migration, get_deals_by_radius() was attempting to validate
API responses as Deal objects. However, this endpoint returns POLYGON METADATA
(with fields: dealscount, polygon_id, settlementNameHeb), NOT individual deals.

All responses failed Pydantic validation (missing dealAmount, dealDate),
resulting in empty lists and 0 deals returned from ALL queries.

## Fixes Applied

### 1. client.py - get_deals_by_radius()
- Return type: `List[Deal]` → `List[Dict[str, Any]]`
- Remove Pydantic validation - return raw metadata dicts
- Update docstring to clarify this returns polygon metadata
- Add note to use find_recent_deals_for_address() for actual deals

### 2. client.py - find_recent_deals_for_address()
- Update to handle polygon metadata dicts (not Deal objects)
- Use dict.get('polygon_id') instead of model attribute access
- Rename variable: `nearby_deals` → `nearby_polygons` for clarity

### 3. fastmcp_server.py - get_deals_by_radius() tool
- Update to handle dict responses (not Deal objects)
- Remove strip_bloat_fields() call (not needed for metadata)
- Update docstring with WARNING about polygon metadata
- Change response keys: "deals" → "polygons", "total_deals" → "total_polygons"

### 4. Tests
- test_govmap_client.py: Update to expect dicts, not Deal objects
- test_fastmcp_tools.py: Update 3 tests to mock dict responses

## Impact
-  find_recent_deals_for_address() NOW WORKS (was returning 0 deals)
-  All 174 tests passing
-  E2E API test confirmed working with real data

## API Behavior Documented
get_deals_by_radius endpoint design:
1. Returns polygon/area metadata (not individual deals)
2. Extract polygon_ids from metadata
3. Call get_street_deals(polygon_id) to get actual deals
4. This workflow is automated in find_recent_deals_for_address()

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 09:38:21 +02:00
Nitzan Pomerantz ff8c7e6509 Complete Phase 4.1 test suite updates - all 174 tests passing
Fixed all remaining test failures after Pydantic v2 migration:

Core fixes:
- Date handling: Convert date objects to ISO strings across 4 files
- Model serialization: Use model_dump(mode='json') for JSON compatibility
- Optional fields: Made time_period_months Optional[int] in models
- Dict access: Replace .get() with getattr() for dynamic attributes

Test updates:
- Updated 50+ test fixtures from dicts to Deal models
- Fixed date-based tests to use recent dates for time filtering
- Added missing imports (CoordinatePoint, MarketActivityScore, DealStatistics)
- Updated assertions from dict keys to model attributes (snake_case)

Files modified:
- nadlan_mcp/govmap/market_analysis.py
- nadlan_mcp/govmap/statistics.py
- nadlan_mcp/govmap/models.py
- nadlan_mcp/fastmcp_server.py
- tests/test_govmap_client.py
- tests/test_fastmcp_tools.py
- .cursor/plans/TEST-UPDATE-STATUS.md (comprehensive documentation)

Result: 174/174 tests passing (100%) 

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:55:42 +02:00
Nitzan Pomerantz 34af8362b9 CR fixes 2025-10-26 23:26:31 +02:00
Nitzan Pomerantz 0ac9d136cd Implementation of phase 4.1 2025-10-26 10:58:46 +02:00
Nitzan Pomerantz 39266ea6e4 Add comprehensive unit tests for Phase 3 refactoring
Test Coverage Expansion:
- Increased from 34 to 138 tests (+304% improvement)
- All tests passing in 0.50s

New Test Files:
1. tests/govmap/test_validators.py (32 tests)
   - Complete coverage for address validation
   - Coordinate validation with ITM bounds checking
   - Positive integer validation with edge cases
   - Deal type validation

2. tests/govmap/test_utils.py (36 tests)
   - Distance calculation tests (Euclidean, diagonal, symmetric)
   - Address matching tests (case sensitivity, substring matching)
   - Comprehensive Hebrew floor parsing (all 12 Hebrew floor names)
   - Edge cases for floor extraction

3. tests/test_fastmcp_tools.py (36 tests)
   - E2E tests for all 10 MCP tools
   - Tests correct JSON formatting
   - Tests bloat field stripping
   - Tests error handling
   - Tests edge cases (no results, invalid input)

Test Quality:
- All edge cases covered
- Hebrew floor names: קרקע, מרתף, ראשונה-עשירית
- Mock-based for fast execution
- Clear test names and documentation

Updated Documentation:
- Updated TEST_COVERAGE_REPORT.md with new test statistics
- Documented all test categories and coverage

Result: Comprehensive test coverage across all refactored modules

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 13:55:37 +03:00
Nitzan Pomerantz 8be71aec55 Update tests/test_govmap_client.py
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-10-25 00:41:24 +03:00
Nitzan Pomerantz ee0b9466d4 Fix: Critical filtering bugs - missing data handling
Fixed two critical bugs in filter_deals_by_criteria():

Bug #1: CRASH on None property type
- Issue: Code called .lower() on None when propertyTypeDescription was null
- Impact: Would crash MCP tool in production with incomplete data
- Fix: Check if deal_type is None/empty before calling .lower()

Bug #2: Missing data passes through filters
- Issue: Deals with None/missing values passed filters when they shouldn't
- Example: Filtering by area=60-70 would include deals with assetArea=None
- Impact: get_valuation_comparables returned inflated results with bad data
- Fix: Explicitly check for None when filter is active and exclude those deals

Changes:
- Property type filter: Check for None/empty before normalization
- Area filter: Exclude deals with missing area when min/max_area specified
- Room filter: Exclude deals with missing rooms when min/max_rooms specified
- Price filter: Exclude deals with missing price when min/max_price specified
- Invalid data: Changed from 'pass' to 'continue' to exclude bad data

Added 6 comprehensive unit tests:
- test_filter_excludes_missing_property_type
- test_filter_excludes_missing_area
- test_filter_excludes_missing_rooms
- test_filter_excludes_missing_price
- test_filter_excludes_invalid_numeric_data
- test_filter_allows_missing_data_when_no_filter

This fixes the E2E issue where get_valuation_comparables was returning
incomplete results. Now filters properly exclude deals with missing data.

Test results: 34/34 passing (6 new tests added)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 00:08:05 +03:00
Nitzan Pomerantz c19af4cd4e Fix: Resolve 2 pre-existing test failures
Fixed both failing tests for 100% test coverage (28/28 passing):

Test 1: test_client_initialization_with_custom_url
- Issue: Test was passing string to GovmapClient constructor
- Fix: Import GovmapConfig and create proper config object
- Changes: Added import and wrapped custom_url in GovmapConfig

Test 2: test_find_recent_deals_for_address_integration
- Issue: Test expected date-first sorting, but function sorts by priority first
- Fix: Updated test expectations to match actual business logic
- Behavior: Results sorted by source priority (building > street > neighborhood), then date
- Changes: Added priority field to mock data and fixed assertions

All 28 tests now passing 

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 23:53:54 +03:00
Nitzan Pomerantz fe0a96ab83 Fix: Address E2E bugs from real-world MCP testing
Fixes three critical bugs discovered during live testing with Claude:

Bug #1: Same building detection always returned 0
- Root cause: deal.get('address') returned empty string
- Fix: Construct address from streetNameHeb + houseNum fields
- Added test: test_same_building_detection_with_api_fields

Bug #2: Property type filter too strict
- Root cause: Exact match failed for variants like 'דירת גג'
- Fix: Use flexible substring matching with normalization

Bug #3: Deal deduplication used non-existent field
- Root cause: Deduplication key referenced deal.get('address')
- Fix: Removed non-existent field from deduplication key

Also added _calculate_distance() helper for future radius filtering.

Test results: 26/28 passing (2 pre-existing failures unrelated)
References: .cursor/plans/MCP-E2E-TEST-SUMMARY.md

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 23:47:07 +03:00
Nitzan Pomerantz 56ed1029e0 Fix tests for time_period_months filtering implementation
Update tests to pass time_period_months=None for tests using old dates
(2022-2023) to disable time filtering. This is necessary because the
time_period_months parameter is now properly implemented and filters
out old deals by default.

Tests fixed:
- test_calculate_market_activity_score_success
- test_calculate_market_activity_score_high_activity
- test_get_market_liquidity_success
- test_get_market_liquidity_quarterly_breakdown

All market analysis tests now pass (25/27 tests passing, 2 pre-existing
failures unrelated to our changes).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 19:36:04 +03:00
Nitzan Pomerantz 943a03ae5a Improve test assertion specificity and formatting
Replace broad assertion (mean > 0) with specific expected value using
pytest.approx for floating-point comparison. This makes the test more
robust and prevents future regressions.

Changes:
- Change area_stats mean assertion from > 0 to == pytest.approx(80.0)
- Add final newline to file per convention

Expected mean: (80 + 90 + 70) / 3 = 80.0

Addresses PR #2 review comments on line 527.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 19:27:32 +03:00
Nitzan Pomerantz 2968711307 Complete Phase 2: Market Analysis, Filtering & Documentation
This commit implements all Phase 2 functionality with architectural
improvements over the original plan.

## Phase 2.1: Property Valuation Data 
- filter_deals_by_criteria() with comprehensive filtering
- calculate_deal_statistics() for statistical aggregations
- _extract_floor_number() for Hebrew floor parsing
- _calculate_std_dev() helper function
- MCP tools: get_valuation_comparables, get_deal_statistics

## Phase 2.2: Market Activity & Investment Analysis 
- calculate_market_activity_score() - deal frequency & velocity
  * Activity score (0-100), trend analysis, monthly distribution
  * Classifies markets: very_high, high, moderate, low, very_low
- analyze_investment_potential() - price trends & stability
  * Price appreciation rate via linear regression
  * Volatility score using coefficient of variation
  * Investment score combining appreciation & stability
- get_market_liquidity() - turnover & liquidity metrics
  * Quarterly/monthly breakdowns, velocity scoring
  * Trend direction, most active periods
- MCP tool: get_market_activity_metrics (unified tool)

## Phase 2.3: Enhanced Deal Filtering 
- Property type, room count, price, area, floor filtering
- All integrated into existing tools
- Hebrew floor number parsing support

## Testing 
- Added 15 comprehensive unit tests (all passing)
- Coverage: market activity, investment analysis, liquidity, filtering
- Edge cases: empty data, invalid dates, insufficient data

## Documentation 
- Created CLAUDE.md (~250 lines) - AI agent guidance
  * Development commands, architecture overview
  * Product vision from USECASES.md
  * Available tools with status indicators
- Updated TASKS.md - Phase 2 marked 100% complete

## Architectural Improvements
- 1 unified MCP tool instead of 6 separate tools (simpler API)
- 1 flexible filtering function instead of 3 (more composable)
- All logic in govmap.py (no new files, better cohesion)
- ~955 lines added with comprehensive documentation

## Design Principles Followed
 MCP provides data, LLM provides intelligence
 No predictions - only statistical calculations
 Comprehensive error handling & input validation
 Well-documented with detailed docstrings

Phase 2 Progress: 100% complete (60% overall project completion)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 18:51:50 +03:00
Nitzan Pomerantz dd6d78b48b Refactor govmap 2025-07-13 01:03:15 +03:00
Nitzan Pomerantz 1f1f4fc0ec Added tests 2025-07-12 17:48:50 +03:00