134 lines
3.5 KiB
Python
134 lines
3.5 KiB
Python
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
# ── Connection points schemas (issue #115) ────────────────────────────────────
|
|
|
|
|
|
class EngineeringStructure(BaseModel):
|
|
name: str | None
|
|
type: str | None
|
|
cad_num: str | None
|
|
distance_to_boundary_m: float
|
|
geometry_geojson: dict[str, Any]
|
|
readable_address: str | None
|
|
raw_props: dict[str, Any]
|
|
source: str
|
|
|
|
|
|
class ZouitOverlap(BaseModel):
|
|
reg_numb_border: str | None
|
|
type_zone: str | None
|
|
subcategory: int | None
|
|
intersects_parcel: bool
|
|
geometry_geojson: dict[str, Any]
|
|
raw_props: dict[str, Any]
|
|
source: str
|
|
|
|
|
|
class ConnectionPointsSummary(BaseModel):
|
|
nearest_structure_distance_m: float | None
|
|
in_protection_zone: bool
|
|
protection_zones_intersecting: int
|
|
total_structures_in_radius: int
|
|
|
|
|
|
class ConnectionPointsResponse(BaseModel):
|
|
engineering_structures: list[EngineeringStructure]
|
|
zouit_engineering_overlaps: list[ZouitOverlap]
|
|
summary: ConnectionPointsSummary
|
|
dump_available: bool
|
|
dump_fetched_at: str | None
|
|
|
|
|
|
# ── Parcel search/detail schemas ──────────────────────────────────────────────
|
|
|
|
|
|
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
|