"""Analytics endpoints for the Sverdlovsk dashboard.""" from __future__ import annotations import re from typing import Annotated, Any, Literal from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from app.core.db import get_db from app.services import analytics_queries as q router = APIRouter() DEV_ID_RE = re.compile(r"^\d+_\d+$") def _validate_dev(dev_id: str) -> str: if not DEV_ID_RE.match(dev_id): raise HTTPException(status_code=400, detail="developer_id must match \\d+_\\d+") return dev_id # ---- Sverdlovsk region ------------------------------------------------------ @router.get("/sverdl/market-pulse") def sverdl_market_pulse(db: Annotated[Session, Depends(get_db)]) -> list[dict[str, Any]]: """Monthly series Jan 2025 → present: total square (тыс м²), sold%, avg price.""" return q.market_pulse(db, region_code=66) @router.get("/sverdl/quartirography") def sverdl_quartirography( db: Annotated[Session, Depends(get_db)], source: Literal["portfolio", "deals"] = "portfolio", ) -> list[dict[str, Any]]: """Что строится (portfolio) vs что покупают (deals) по сегментам.""" return q.quartirography(db, source=source, region_id=66) @router.get("/sverdl/pipeline") def sverdl_pipeline(db: Annotated[Session, Depends(get_db)]) -> list[dict[str, Any]]: """По году ввода: total / sold% / unsold% / unopened%.""" return q.pipeline_by_year(db, region_code=66) @router.get("/sverdl/districts") def sverdl_districts(db: Annotated[Session, Depends(get_db)]) -> list[dict[str, Any]]: """ЕКБ-районы: ЖК-count, flat-count, area, цены.""" return q.districts(db) @router.get("/sverdl/yandex-listings") def sverdl_yandex_listings(db: Annotated[Session, Depends(get_db)]) -> dict[str, Any]: """Снимок активных новостроек Яндекс.Недвижимости.""" return q.yandex_listings(db) # ---- Developers ------------------------------------------------------------- @router.get("/developers/top") def developers_top( db: Annotated[Session, Depends(get_db)], limit: Annotated[int, Query(ge=1, le=50)] = 15, ) -> list[dict[str, Any]]: """Топ-девелоперы Свердл по объёму с Δ sold% за всю доступную историю.""" return q.top_developers(db, region_code=66, limit=limit) @router.get("/developers/history") def developers_history( db: Annotated[Session, Depends(get_db)], ids: Annotated[str, Query(min_length=1)], ) -> list[dict[str, Any]]: """Comma-separated developer_ids → time series sold_perc.""" dev_ids = [_validate_dev(i.strip()) for i in ids.split(",") if i.strip()] if not dev_ids: raise HTTPException(status_code=400, detail="ids must contain at least one developer_id") return q.developer_history(db, developer_ids=dev_ids, region_code=66) @router.get("/developers/{developer_id}") def developer_detail( db: Annotated[Session, Depends(get_db)], developer_id: str, ) -> dict[str, Any]: dev = _validate_dev(developer_id) result = q.developer_detail(db, developer_id=dev) if result is None: raise HTTPException(status_code=404, detail="developer not found") return result @router.get("/developers/{developer_id}/portfolio") def developer_portfolio( db: Annotated[Session, Depends(get_db)], developer_id: str, ) -> list[dict[str, Any]]: dev = _validate_dev(developer_id) return q.developer_portfolio(db, developer_id=dev) # ---- PRINZIP-specific ------------------------------------------------------- @router.get("/prinzip/insights") def prinzip_insights() -> dict[str, Any]: """Static recommendations + benchmarks (from PRINZIP_Strategy_Apr27).""" return q.prinzip_insights() @router.get("/prinzip/districts") def prinzip_districts(db: Annotated[Session, Depends(get_db)]) -> list[dict[str, Any]]: return q.prinzip_district_distribution(db, developer_id="6208_0")