Newer
Older
vmk-360_data_mcp / src / vmk_data_mcp / models.py
"""Pydantic-модели для входных параметров и выходных результатов инструментов."""

from datetime import date
from typing import Literal

from pydantic import BaseModel, Field


class PaginationParams(BaseModel):
    """Базовая пагинация."""

    limit: int = Field(default=20, ge=1, le=100, description="Количество результатов на странице")
    offset: int = Field(default=0, ge=0, description="Смещение для пагинации")


class MetadataFilters(BaseModel):
    """Фильтры по метаданным, общие для всех инструментов поиска."""

    deal_type: Literal["sale", "rent_long", "rent_short"] | None = Field(
        default=None, description="Тип сделки"
    )
    city: str | None = Field(default=None, description="Город (украинский)")
    district: str | None = Field(default=None, description="Район (украинский)")
    rooms_count: int | None = Field(default=None, ge=0, description="Количество комнат")
    min_price: float | None = Field(default=None, ge=0, description="Минимальная цена")
    max_price: float | None = Field(default=None, ge=0, description="Максимальная цена")
    currency: Literal["USD", "EUR", "UAH"] | None = Field(default=None, description="Валюта")
    min_total_area: float | None = Field(
        default=None, ge=0, description="Минимальная общая площадь (м²)"
    )
    max_total_area: float | None = Field(
        default=None, ge=0, description="Максимальная общая площадь (м²)"
    )
    building_type: Literal["brick", "panel", "monolith", "gas_block", "wood"] | None = Field(
        default=None, description="Тип постройки"
    )
    floor: int | None = Field(default=None, ge=0, description="Этаж")
    listing_status: Literal["active", "sold", "rented", "removed", "archived"] | None = Field(
        default=None, description="Статус объявления"
    )
    metro_station: str | None = Field(default=None, description="Станция метро (украинский)")


class SearchSimilarInput(BaseModel):
    """Входные параметры для векторного поиска."""

    query: str = Field(..., description="Текстовый запрос на украинском языке для поиска по смыслу")
    filters: MetadataFilters = Field(default_factory=MetadataFilters)
    pagination: PaginationParams = Field(default_factory=PaginationParams)
    min_similarity: float = Field(
        default=0.7, ge=0.0, le=1.0, description="Минимальный порог косинусной близости"
    )


class SearchMetadataInput(BaseModel):
    """Входные параметры для поиска по метаданным/FTS."""

    query: str = Field(
        ..., description="Текстовый запрос на украинском языке для полнотекстового поиска"
    )
    filters: MetadataFilters = Field(default_factory=MetadataFilters)
    pagination: PaginationParams = Field(default_factory=PaginationParams)


class GetListingInput(BaseModel):
    """Входные параметры для получения объявления по ID."""

    listing_id: int = Field(..., ge=1, description="ID объявления")


class ListingResult(BaseModel):
    """Результат поиска — одно объявление."""

    id: int
    title: str
    description: str | None = None
    generated_description: str | None = None
    price: float | None = None
    currency: str | None = None
    deal_type: str | None = None
    city: str | None = None
    district: str | None = None
    rooms_count: int | None = None
    total_area: float | None = None
    living_area: float | None = None
    kitchen_area: float | None = None
    floor: int | None = None
    floors_count: int | None = None
    building_type: str | None = None
    building_year: int | None = None
    renovation_status: str | None = None
    balcony_count: int | None = None
    bathroom_type: str | None = None
    parking_type: str | None = None
    heating_type: str | None = None
    layout_type: str | None = None
    window_view: str | None = None
    metro_station: str | None = None
    metro_distance_type: str | None = None
    metro_distance_meters: int | None = None
    url_source: str | None = None
    publish_date: date | None = None
    images_count: int | None = None
    contact_phone: str | None = None
    listing_status: str | None = None
    archived_at: date | None = None
    created_at: date | None = None
    updated_at: date | None = None
    similarity_score: float | None = None  # для векторного поиска
    rank_score: float | None = None  # для FTS


class SearchResult(BaseModel):
    """Результат поиска — список объявлений + мета."""

    total: int = Field(description="Общее количество найденных записей")
    limit: int
    offset: int
    listings: list[ListingResult]