Разработка AI-системы для подбора объектов недвижимости

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI-системы для подбора объектов недвижимости
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

AI-система подбора недвижимости

Поиск недвижимости — многомерная задача с высокой ценой ошибки. Покупатель не всегда точно формулирует критерии: «уютная квартира рядом с метро» нужно перевести в конкретные параметры. ML-система понимает неявные предпочтения из истории просмотров и уточняет профиль через диалог.

Модель предпочтений из поведения

import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.metrics.pairwise import cosine_similarity
from anthropic import Anthropic

class PropertyPreferenceModel:
    """Извлечение предпочтений пользователя из истории просмотров"""

    def __init__(self):
        self.scaler = StandardScaler()
        self.llm = Anthropic()

    def build_preference_vector(self, viewed_properties: list[dict],
                                 saved_properties: list[dict],
                                 contacted_properties: list[dict]) -> np.ndarray:
        """
        Взвешенный профиль из разных типов взаимодействий.
        Вес: просмотр=1, сохранение=3, контакт=5
        """
        weighted_features = []

        for prop_list, weight in [
            (viewed_properties, 1.0),
            (saved_properties, 3.0),
            (contacted_properties, 5.0)
        ]:
            for prop in prop_list:
                features = self._extract_features(prop)
                weighted_features.append(features * weight)

        if not weighted_features:
            return None

        # Взвешенное среднее профиль
        return np.mean(weighted_features, axis=0)

    def _extract_features(self, property: dict) -> np.ndarray:
        """Числовой вектор объекта недвижимости"""
        return np.array([
            property.get('price_m2', 0) / 200000,        # Нормализованная цена/м²
            property.get('area_m2', 0) / 150,             # Площадь
            property.get('rooms', 0) / 5,                 # Комнат
            property.get('floor', 0) / 25,                # Этаж
            property.get('floor_total', 0) / 25,          # Этажность дома
            property.get('metro_minutes', 99) / 60,       # Минут до метро
            int(property.get('new_building', False)),      # Новостройка
            int(property.get('has_parking', False)),       # Парковка
            int(property.get('balcony', False)),           # Балкон
            property.get('ceiling_height', 2.5) / 4.0,    # Высота потолков
            int(property.get('renovation', 'none') == 'euro'),  # Евроремонт
            int(property.get('renovation', 'none') == 'designer'),
            property.get('year_built', 1990) / 2024,      # Год постройки
        ])

    def find_similar_properties(self, user_preference: np.ndarray,
                                  candidates: list[dict],
                                  top_k: int = 20) -> list[dict]:
        """Поиск похожих объектов по косинусному сходству"""
        if user_preference is None:
            return candidates[:top_k]

        candidate_features = np.array([
            self._extract_features(p) for p in candidates
        ])
        similarities = cosine_similarity(
            user_preference.reshape(1, -1), candidate_features
        )[0]

        for i, prop in enumerate(candidates):
            prop['match_score'] = float(similarities[i])

        return sorted(candidates, key=lambda x: x['match_score'], reverse=True)[:top_k]


class PropertySearchAssistant:
    """Диалоговый агент для уточнения параметров поиска"""

    def __init__(self):
        self.llm = Anthropic()
        self.conversation = []

    def chat(self, user_message: str, current_filters: dict,
              sample_properties: list[dict]) -> dict:
        """Обработка пользовательского сообщения, обновление фильтров"""
        self.conversation.append({"role": "user", "content": user_message})

        import json
        response = self.llm.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=400,
            system="""You are a real estate search assistant. Help users find properties.
Extract search filters from conversation. Respond in Russian.

Current filters (JSON): """ + json.dumps(current_filters, ensure_ascii=False) + """

Sample properties found: """ + str(len(sample_properties)) + """ objects

For each user message:
1. Update search filters based on what they said
2. Ask 1 clarifying question if important parameters are missing
3. Summarize what you understood

Return JSON: {"filters": {...}, "clarifying_question": "...", "summary": "..."}""",
            messages=self.conversation
        )

        assistant_text = response.content[0].text
        self.conversation.append({"role": "assistant", "content": assistant_text})

        try:
            parsed = json.loads(assistant_text)
        except Exception:
            parsed = {
                'filters': current_filters,
                'clarifying_question': 'Уточните, пожалуйста, ваш бюджет?',
                'summary': assistant_text
            }

        return parsed

    def explain_recommendation(self, property: dict,
                                user_preference: np.ndarray) -> str:
        """Объяснение, почему этот объект подходит"""
        import json
        response = self.llm.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=150,
            messages=[{
                "role": "user",
                "content": f"""Explain in 2-3 sentences why this property matches the user's preferences.
Property: {json.dumps(property, ensure_ascii=False)}
Match score: {property.get('match_score', 0):.0%}
Speak Russian, be specific about the best features."""
            }]
        )
        return response.content[0].text


class NeighborhoodScorer:
    """Скоринг района по POI (Points of Interest)"""

    def score_location(self, lat: float, lon: float,
                        user_priorities: dict) -> dict:
        """
        user_priorities: {'schools': 3, 'metro': 5, 'parks': 2, 'shopping': 1}
        POI данные получаются из OpenStreetMap / Яндекс Геосервисов
        """
        # Заглушка для реальных API-вызовов
        poi_distances = {
            'metro': 8,      # минут пешком
            'schools': 12,
            'parks': 5,
            'shopping': 15,
            'hospitals': 20,
        }

        score = 0
        max_score = sum(user_priorities.values())

        for poi_type, priority in user_priorities.items():
            distance = poi_distances.get(poi_type, 30)
            # Оценка: 10 мин = хорошо, 20+ мин = плохо
            poi_score = max(0, 1 - (distance - 5) / 20)
            score += poi_score * priority

        return {
            'total_score': round(score / max_score, 2),
            'poi_breakdown': {k: v for k, v in poi_distances.items()},
            'walkability': self._compute_walkability(poi_distances)
        }

    def _compute_walkability(self, poi_distances: dict) -> str:
        avg_distance = np.mean(list(poi_distances.values()))
        if avg_distance < 10:
            return 'excellent'
        elif avg_distance < 15:
            return 'good'
        elif avg_distance < 20:
            return 'average'
        return 'poor'

Интеграция с ценовой аналитикой

Система автоматически помечает объекты как «выгодные» или «переоценённые» на основе регрессионной модели справедливой цены:

class PropertyPriceEstimator:
    def assess_value(self, property: dict, market_data: pd.DataFrame) -> dict:
        """Оценка рыночной справедливости цены"""
        # GBT модель обучена на транзакциях последних 6 месяцев
        similar = market_data[
            (market_data['district'] == property.get('district')) &
            (market_data['rooms'] == property.get('rooms')) &
            (abs(market_data['area_m2'] - property.get('area_m2', 0)) < 15)
        ]

        if len(similar) < 5:
            return {'assessment': 'insufficient_data'}

        market_price_m2 = similar['price_m2'].median()
        property_price_m2 = property.get('price', 0) / max(property.get('area_m2', 1), 1)

        premium_pct = (property_price_m2 - market_price_m2) / market_price_m2 * 100

        if premium_pct < -10:
            assessment = 'underpriced'
        elif premium_pct > 15:
            assessment = 'overpriced'
        else:
            assessment = 'fair_price'

        return {
            'assessment': assessment,
            'market_price_m2': round(market_price_m2),
            'property_price_m2': round(property_price_m2),
            'premium_pct': round(premium_pct, 1),
            'similar_count': len(similar)
        }

Типичные результаты внедрения: время поиска объекта сокращается с 3-6 недель до 1-2, количество нерелевантных просмотров снижается на 40-60%. Конверсия в звонок брокеру растёт на 20-30% за счёт предварительной квалификации интереса через диалог.