# Nadlan-MCP — חיפוש נדל"ן ושמאי מכריע כלי web ו-MCP חינמי וקוד פתוח לחיפוש מידע נדל"ני בישראל: עסקאות נדל"ן עדכניות מ-Govmap והחלטות שמאי מכריע ממשרד המשפטים — בממשק עברי מלא עם תמיכת RTL. > **TL;DR:** הכנס כתובת, גוש+חלקה או שם של שמאי מכריע — מקבל בו-זמנית עסקאות באזור והחלטות שומה רלוונטיות, כולל הורדת PDF. --- ## תוכן עניינים - [מה הכלי עושה](#מה-הכלי-עושה) - [שני ממשקים — Web ו-MCP](#שני-ממשקים--web-ו-mcp) - [תכונות עיקריות](#תכונות-עיקריות) - [אופני חיפוש](#אופני-חיפוש) - [ארכיטקטורה](#ארכיטקטורה) - [התקנה והפעלה](#התקנה-והפעלה) - [API Endpoints](#api-endpoints) - [פריסה (Deployment)](#פריסה-deployment) - [פיתוח](#פיתוח) - [ניהול בעיות RTL](#ניהול-בעיות-rtl) - [רישוי](#רישוי) --- ## מה הכלי עושה הכלי מחבר בין שני מקורות מידע ממשלתיים שלא היו זמינים יחד עד היום: | מקור | מה מספק | תדירות עדכון | |------|---------|--------------| | **Govmap** (`www.govmap.gov.il`) | עסקאות נדל"ן רשומות מ-2010 ואילך — מחיר, שטח, חדרים, קומה, גוש/חלקה/תת-חלקה | חודשי | | **משרד המשפטים** (`pub-justice.openapi.gov.il`) | החלטות שמאי מכריע — שומות הכרעה, ערעורים, היטלי השבחה, פיצויי הפקעה, וכד' | שוטף | המידע מוצג בטבלה אחת, מסונכרן בין המקורות לפי גוש/חלקה, עם קישורים ישירים ל-PDF של ההחלטות. --- ## שני ממשקים — Web ו-MCP ### 1. ממשק Web (React) ממשק משתמש מלא לבני אדם: ``` https://nadlan-mcp.dev.marcus-law.co.il ``` טופס חיפוש, שתי טבלאות (עסקאות + שומות), פתיחה והורדה של PDF, RTL מלא. ### 2. ממשק MCP (Model Context Protocol) שרת MCP ל-LLMs ול-AI agents — מאפשר ל-Claude / ChatGPT / כל סוכן AI לחפש בעצמו מידע נדל"ני. נחשף ב-`/mcp` כ-streamable HTTP. כולל 10 כלים: - `autocomplete_address` — חיפוש כתובת - `find_recent_deals_for_address` — עסקאות סביב כתובת - `analyze_market_trends` — ניתוח מגמות מחירים - `compare_addresses` — השוואה בין שתי כתובות - `get_valuation_comparables` — השוואות שמאיות - `get_deal_statistics` — סטטיסטיקה מצרפית - `get_market_activity_metrics` — מדדי פעילות שוק - ועוד. ראה `nadlan_mcp/fastmcp_server.py`. --- ## תכונות עיקריות ### 🔍 שלושה אופני חיפוש בו-זמנית חיפוש לפי **כתובת** (`קטלב 5 צור הדסה`), לפי **גוש+חלקה** (`גוש 6212 חלקה 894`) או לפי **שם של שמאי מכריע** (`לוי גלבוע`). הממשק מתאים את עצמו אוטומטית. ### 🏢 זיהוי "אותו רחוב" חכם כשמחפשים לפי גוש+חלקה, ה-Govmap מחזיר עסקאות מהפוליגון של הרחוב — אבל פוליגון יכול להכיל מספר רחובות. הפתרון שלנו: מציאת העסקה עם מספר חלקה הקרוב ביותר לחלקה המחופשת בתוך אותו גוש — סביר מאוד שהיא באותו רחוב. כך אנחנו מסמנים נכון מה "אותו רחוב" ומה "סביבה קרובה". ### 📊 שלוש רמות לשומות שמאי מכריע כל חיפוש לפי גוש+חלקה מחזיר אוטומטית שלוש רמות: 1. **החלטות תואמות** — בדיוק הגוש+חלקה שחיפשת 2. **החלטות נוספות באותו גוש** — חלקות אחרות באותו גוש 3. **החלטות בגושים סמוכים** — גוש ±2 דה-דופליקציה מובנית מבטיחה שהחלטה לא תופיע פעמיים. ### 📄 PDF Proxy החלטות שמאי מכריע מאוחסנות בשרת ממשלתי שדורש header של `x-client-id`. דפדפן רגיל לא יכול להוריד אותן ישירות. הכלי מספק proxy endpoint שמטפל בזה — עם whitelist של hosts מותרים למניעת SSRF. ### 🛡️ עקיפת WAF של F5 ה-API של משרד המשפטים מוגן ב-WAF של F5 שחוסם בקשות "לא דפדפניות". הכלי משתמש ב-`curl_cffi` עם impersonation של Chrome 120 (TLS fingerprint תואם דפדפן אמיתי) כדי לעבור את ה-WAF. ### 🌐 RTL מלא תמיכה מלאה בעברית — כולל `DirectionProvider` של Radix, `dir="rtl"` מפורש על body/tables/Tabs, ועיצוב Tailwind עם classes לוגיים (`text-start`/`text-end`/`ms-*`/`me-*`). ### ⚡ זיהוי חריגים (Outlier Detection) ניתוחים סטטיסטיים משתמשים בפילטר IQR + סינון אחוזי + גבולות קשיחים, להסרת עסקאות עם נתונים שגויים (טעויות הקלדה, מכירות חלקיות, מקרים חריגים). --- ## אופני חיפוש ### חיפוש לפי כתובת ``` קטלב 5 צור הדסה הילדסהיימר 14 תל אביב ויצמן 10 חיפה ``` מחזיר את העסקאות באזור + השומות התואמות לגוש שזוהה. ### חיפוש לפי גוש + חלקה מתאים כשאין כתובת מדויקת או כשמחפשים פרצל גולמי: ``` גוש: 6212 חלקה: 894 ``` פנימית, נשלח ל-Govmap בפורמט `גוש N חלקה M` (Govmap מזהה את הפורמט הזה ב-autocomplete). ### חיפוש לפי שמאי מכריע מציג את כל ההחלטות שאותו שמאי הכריע בהן: ``` לוי גלבוע גלית עוזרי ``` לא מציג עסקאות (אין רלוונטיות אזורית). --- ## ארכיטקטורה ``` ┌────────────────────────────────────────────────────────────────┐ │ דפדפן (React 19 + Vite) │ │ ─ SearchBar (3 modes) │ │ ─ DealsTable (proximity inference, gush/חלקה columns) │ │ ─ AppraisalsTable (3 tiers: exact / same block / nearby) │ │ ─ TanStack Query, shadcn/ui, RTL DirectionProvider │ └──────────────────────────────────┬─────────────────────────────┘ │ REST + JSON ▼ ┌────────────────────────────────────────────────────────────────┐ │ FastAPI + FastMCP (Python 3.13) │ │ │ │ /api/search/address ─→ Govmap autocomplete │ │ /api/search/deals ─→ Govmap deals (radius+polygon) │ │ /api/search/appraisals ─→ gov.il SearchDecisions (3 tiers) │ │ /api/appraisals/pdf ─→ Streaming PDF proxy w/ x-client-id │ │ /mcp ─→ FastMCP streamable-http (LLMs) │ │ / ─→ Static (React build) │ └────────────────────────────────────────────────────────────────┘ │ │ ▼ ▼ ┌──────────────────┐ ┌────────────────────────────┐ │ www.govmap.gov │ │ pub-justice.openapi.gov.il │ │ (no auth) │ │ (x-client-id header, │ │ │ │ curl_cffi Chrome120 TLS) │ └──────────────────┘ └────────────────────────────┘ ``` **שכבות הקוד:** ``` nadlan-mcp/ ├── nadlan_mcp/ │ ├── fastmcp_server.py # FastMCP tools (10) + clients │ ├── web_app.py # FastAPI app + REST endpoints + static │ ├── config.py # GovmapConfig + env vars │ ├── govmap/ # Govmap API client (Pydantic v2 models) │ │ ├── client.py # HTTP, retry, rate limiting │ │ ├── models.py # Deal, AutocompleteResponse, etc. │ │ ├── filters.py # Deal filtering │ │ ├── statistics.py # Statistical analysis + outlier filtering │ │ ├── market_analysis.py # Trends, comparables │ │ └── outlier_detection.py │ └── govil/ # gov.il decisive-appraiser client │ ├── client.py # curl_cffi w/ Chrome impersonation │ └── models.py # AppraisalDecision, AppraisalDocument ├── web/ # React 19 + Vite + shadcn/ui frontend │ ├── src/ │ │ ├── App.tsx │ │ ├── api/{client,types}.ts │ │ └── components/ │ │ ├── SearchBar.tsx │ │ ├── DealsTable.tsx │ │ ├── AppraisalsTable.tsx │ │ └── ui/ # shadcn-ui primitives (RTL-patched) │ └── package.json ├── tests/ # 314 tests, 84% coverage ├── Dockerfile # Multi-stage: node build → python runtime └── .gitea/workflows/deploy.yaml # CI/CD: build → registry → Coolify ``` --- ## התקנה והפעלה ### דרישות - Python 3.13+ - Node 22+ (לבניית ה-frontend) - pip / venv ### הרצה לוקלית ```bash # 1) שכפול git clone https://gitea.dev.marcus-law.co.il/mcp-servers/nadlan-mcp.git cd nadlan-mcp # 2) Python — סביבה וירטואלית python3 -m venv venv source venv/bin/activate pip install -e . # 3) בניית ה-frontend cd web npm install npm run build cd .. # 4) הרצת השרת PORT=8000 python run_http_server.py ``` פתח ב-`http://127.0.0.1:8000`. ### הרצה עם Docker ```bash docker build -t nadlan-mcp . docker run -p 8000:8000 nadlan-mcp ``` ### משתני סביבה | משתנה | ברירת מחדל | תיאור | |--------|------------|--------| | `PORT` | `8000` | פורט השרת | | `GOVMAP_BASE_URL` | `https://www.govmap.gov.il/api/` | בסיס API של Govmap | | `GOVMAP_REQUESTS_PER_SECOND` | `5.0` | rate-limit ל-Govmap | | `GOVMAP_MAX_RETRIES` | `3` | retry exponential backoff | | `GOVMAP_DEFAULT_RADIUS` | `50` | רדיוס ברירת מחדל (מטרים) | | `ANALYSIS_OUTLIER_METHOD` | `iqr` | שיטת זיהוי חריגים (`iqr`/`percent`/`none`) | | `ANALYSIS_PRICE_PER_SQM_MIN` | `1000` | גבול תחתון נמוך מדי למחיר/מ"ר | | `ANALYSIS_PRICE_PER_SQM_MAX` | `100000` | גבול עליון | ראה רשימה מלאה ב-`nadlan_mcp/config.py`. --- ## API Endpoints כל ה-endpoints נחשפים תחת `/api/` (גם `/api/docs` לתיעוד OpenAPI/Swagger). ### `GET /api/search/address?q=` Autocomplete של כתובת — מחזיר קואורדינטות + גוש/חלקה אם זוהו. ```bash curl "https://nadlan-mcp.dev.marcus-law.co.il/api/search/address?q=הילדסהיימר%2014%20תל%20אביב" ``` ### `POST /api/search/deals` חיפוש עסקאות סביב כתובת. ```json { "address": "גוש 6212 חלקה 894", "years_back": 3, "radius_meters": 100, "max_deals": 50, "deal_type": 2 } ``` תגובה כוללת `search.resolved_address` (הטקסט המפוענח של Govmap), `total`, ו-`deals[]` עם השדות `streetNameHeb`, `houseNum`, `gushNum`, `parcelNum`, `subParcelNum`, `deal_amount`, `asset_area`, `price_per_sqm`, `deal_source` (`same_building` / `street` / `neighborhood`), ועוד. ### `POST /api/search/appraisals` חיפוש החלטות שמאי מכריע. כשמעבירים `include_nearby: true` עם block, מחזיר 3 buckets: ```json { "block": "6212", "plot": "894", "max_results": 30, "include_nearby": true, "nearby_block_radius": 2 } ``` תגובה: ```json { "total_in_db": 13420, "returned": 5, "decisions": [...], "same_block": { "count": 12, "decisions": [...] }, "nearby_blocks": { "count": 8, "decisions": [...] } } ``` ### `GET /api/appraisals/pdf?url=` Proxy ל-PDF של החלטה. רק hosts ב-whitelist (`pub-justice.openapi.gov.il`). הוסף `&download=1` לכפיית הורדה. ### `/mcp` FastMCP streamable-http endpoint — לחיבור LLMs ו-AI agents. --- ## פריסה (Deployment) הפריסה ל-`nadlan-mcp.dev.marcus-law.co.il` אוטומטית דרך CI/CD: ``` git push → Gitea Actions → build Docker image → push to private registry → trigger Coolify webhook → pull + redeploy ``` ראה `.gitea/workflows/deploy.yaml`. **Stack:** - Coolify (orchestration) - Traefik 3.6 (reverse proxy + Let's Encrypt) - Docker registry פרטי - Python 3.13 slim image --- ## פיתוח ### בדיקות ```bash # כל הבדיקות pytest # רק unit tests pytest -m unit # כיסוי pytest --cov=nadlan_mcp # בדיקת איכות מלאה (lint + format + tests) ./check-quality.sh ``` ### Linting ```bash ruff format . # פורמט ruff check . --fix # תיקון אוטומטי ``` ### פיתוח Frontend ```bash cd web npm run dev # Vite dev server (HMR) npm run typecheck # tsc --noEmit npm run lint ``` --- ## ניהול בעיות RTL זיהינו והתגברנו על מספר בעיות RTL בדרך הקשה. אם אתה בונה אפליקציית React בעברית, מומלץ: 1. **הגדרה כפולה:** `` **וגם** `` — חלק מהספריות בודקות את `document.body.dir` ולא את `documentElement`. 2. **CSS מפורש:** `html, body { direction: rtl; }` ב-base layer של Tailwind. 3. **DirectionProvider:** עטוף את ה-tree של React ב-`` של `@radix-ui/react-direction`. בלי זה, רכיבי Radix (Tabs, Dialog, Select, Popover) ברירת המחדל שלהם היא LTR. 4. **Tabs wrapper:** עטוף את `TabsPrimitive.Root` עם default `dir="rtl"` כדי שגם אם DirectionProvider נמחק בעתיד, Tabs יישאר RTL. 5. **Tables:** `` מפורש על כל טבלה — מבטיח סדר עמודות נכון בכל הדפדפנים. 6. **Logical classes:** השתמש ב-`text-start`/`text-end` (לא `text-left`/`text-right`), `ms-*`/`me-*` (לא `ml-*`/`mr-*`), `start-*`/`end-*`. אבל זכור: ב-RTL, **`text-end` מצמיד לשמאל**, לא לימין. 7. **בדיקה ויזואלית בדפדפן** לפני הצהרה ש-task UI הסתיים. build מוצלח לא מספיק. --- ## רישוי MIT — ראה `LICENSE`. --- ## קישורים - 🌐 **Web app:** https://nadlan-mcp.dev.marcus-law.co.il - 📦 **Repo:** https://gitea.dev.marcus-law.co.il/mcp-servers/nadlan-mcp - 📚 **API docs (Swagger):** https://nadlan-mcp.dev.marcus-law.co.il/api/docs - 🛠 **MCP endpoint:** https://nadlan-mcp.dev.marcus-law.co.il/mcp - 📖 **תיעוד אדריכלי:** `ARCHITECTURE.md` - 🚀 **תיעוד פריסה:** `DEPLOYMENT.md` - 📝 **שינויים:** `CHANGELOG.md`