Some checks failed
CI / backend (push) Successful in 1m16s
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Failing after 12s
Deploy / build-worker (push) Failing after 12s
Deploy / build-frontend (push) Failing after 0s
Deploy / deploy (push) Failing after 0s
CI / frontend (push) Successful in 2m17s
93 lines
2.3 KiB
Python
93 lines
2.3 KiB
Python
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class ParcelFilter(BaseModel):
|
|
vri: list[str] | None = None
|
|
area_min_sqm: float | None = None
|
|
area_max_sqm: float | None = None
|
|
region: str | None = None
|
|
polygon_geojson: dict[str, Any] | None = None
|
|
text_search: str | None = None
|
|
|
|
|
|
class ScoringWeights(BaseModel):
|
|
distance_to_center: float = 0.4
|
|
poi_density: float = 0.3
|
|
neighborhood_price: float = 0.3
|
|
|
|
|
|
class ParcelSearchRequest(BaseModel):
|
|
filters: ParcelFilter = Field(default_factory=ParcelFilter)
|
|
weights: ScoringWeights = Field(default_factory=ScoringWeights)
|
|
limit: int = 50
|
|
|
|
|
|
class ScoreBreakdown(BaseModel):
|
|
total: float
|
|
distance_to_center: float
|
|
poi_density: float
|
|
neighborhood_price: float
|
|
|
|
|
|
class ParcelSummary(BaseModel):
|
|
id: str
|
|
cadastral_number: str
|
|
area_sqm: float
|
|
vri: str
|
|
address: str | None
|
|
score: ScoreBreakdown
|
|
|
|
|
|
class ParcelSearchResponse(BaseModel):
|
|
items: list[ParcelSummary]
|
|
total: int
|
|
|
|
|
|
class ParcelDetail(ParcelSummary):
|
|
geometry_geojson: dict[str, Any]
|
|
enrichment: dict[str, Any]
|
|
|
|
|
|
# ── Competitors endpoint schemas ──────────────────────────────────────────────
|
|
|
|
TimeWindow = Literal["last_month", "last_quarter", "last_year"]
|
|
ObjClassFilter = Literal["economy", "comfort", "business"]
|
|
|
|
|
|
class CompetitorsRequest(BaseModel):
|
|
radius_km: float = Field(default=1.0, ge=0.1, le=1.5)
|
|
time_window: TimeWindow = "last_quarter"
|
|
obj_class_filter: ObjClassFilter | None = None
|
|
exclude_obj_ids: list[int] = Field(default_factory=list)
|
|
|
|
|
|
class Competitor(BaseModel):
|
|
obj_id: int
|
|
comm_name: str | None
|
|
dev_name: str | None
|
|
obj_class: str | None
|
|
distance_m: float
|
|
lat: float
|
|
lng: float
|
|
stage: str | None
|
|
flats_total: int | None
|
|
flats_sold: int | None
|
|
sold_pct: float | None
|
|
velocity_per_month: float
|
|
avg_price_per_m2: float | None
|
|
is_active: bool
|
|
|
|
|
|
class CompetitorsSummary(BaseModel):
|
|
total_competitors: int
|
|
active_count: int
|
|
weighted_avg_velocity: float
|
|
radius_km: float
|
|
time_window: str
|
|
|
|
|
|
class CompetitorsResponse(BaseModel):
|
|
competitors: list[Competitor]
|
|
summary: CompetitorsSummary
|