POST /api/v1/search reading listings_search_mv with ~20 cross-source filters, parameterized SQL (CAST(:x AS type), psycopg v3), whitelisted ORDER BY (Pydantic Literal), Redis 5min hot cache with graceful degradation (singleton pool via lru_cache). Verified vs data/sql/050_search_optimization.sql: matview column refs (total_area, lng, house_rating, sources[], has_avito/cian/yandex), SQL injection safety, cache failure swallow, router prefix. Deep-code-reviewer: APPROVE.
53 lines
1.3 KiB
Python
53 lines
1.3 KiB
Python
"""SearchResponse + SearchResultItem — /api/v1/search return shape."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel, ConfigDict
|
|
|
|
|
|
class SearchResultItem(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
listing_id: int
|
|
source: str
|
|
source_url: str | None = None
|
|
address: str | None = None
|
|
lat: float | None = None
|
|
lng: float | None = None
|
|
|
|
rooms: int | None = None
|
|
total_area: float | None = None
|
|
floor: int | None = None
|
|
total_floors: int | None = None
|
|
|
|
price_rub: int | None = None
|
|
price_per_m2: int | None = None
|
|
kadastr_num: str | None = None
|
|
scraped_at: datetime | None = None
|
|
|
|
house_id: int | None = None
|
|
year_built: int | None = None
|
|
house_class: str | None = None
|
|
developer_name: str | None = None
|
|
house_rating: float | None = None
|
|
house_ratings_count: int | None = None
|
|
|
|
source_count: int | None = None
|
|
sources: list[str] | None = None
|
|
has_avito: bool | None = None
|
|
has_cian: bool | None = None
|
|
has_yandex: bool | None = None
|
|
|
|
house_median_ppm2: float | None = None
|
|
district: str | None = None
|
|
|
|
|
|
class SearchResponse(BaseModel):
|
|
items: list[SearchResultItem]
|
|
total: int
|
|
page: int
|
|
page_size: int
|
|
elapsed_ms: float | None = None
|
|
cache_hit: bool = False
|