gendesign/tradein-mvp/backend/app/schemas/search_response.py
lekss361 7814e1223a feat(tradein): Phase 3.2 — /api/v1/search endpoint + Redis cache
Multi-Source Integration Phase 3.2. /api/v1/search with cross-source filters
+ POST body params + Redis 5min hot cache.

New files:
  app/schemas/search.py            — SearchParams (~20 filters, Pydantic v2)
  app/schemas/search_response.py   — SearchResponse + SearchResultItem
  app/services/search_query.py     — build_search_query (psycopg v3, CAST(:x AS type))
  app/services/cache.py            — SearchCache async wrapper (singleton lru_cache)
  app/api/v1/search.py             — POST /search endpoint
  tests/test_search_api.py         — unit + integration tests

Edited:
  app/main.py                      — include search.router prefix=/api/v1
  app/core/config.py               — settings.redis_url default
  pyproject.toml                   — add redis>=5.0.0

Queries listings_search_mv (matview from PR #469). Cache failures swallowed
(degrade to slow-but-correct). Sort whitelisted dict (no SQL injection).

Refs Master Plan sec 9.1 + 7.2.
2026-05-23 17:38:00 +03:00

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