"""Pydantic-схемы admin read API над `user_events` (Feature 2 audit + Feature 3 analytics). `user_events` (migration `184_user_events.sql`) — unified append-only событийный лог (login/IP audit + behavior analytics), admin-read-only. Эти схемы описывают ответы `GET /api/v1/admin/audit/*` и `GET /api/v1/admin/analytics` (см. app/api/v1/audit.py). """ from __future__ import annotations from datetime import date, datetime from pydantic import BaseModel, ConfigDict class AccountSummary(BaseModel): """Одна строка списка `GET /audit/accounts` — сводка по одному username.""" model_config = ConfigDict(from_attributes=True) username: str first_seen_at: datetime last_seen_at: datetime distinct_ips: int distinct_devices: int login_count: int request_count: int search_count: int class AccountIpEntry(BaseModel): """Одна строка `ips` в drilldown `GET /audit/accounts/{username}`.""" model_config = ConfigDict(from_attributes=True) ip_address: str | None = None event_count: int first_seen: datetime last_seen: datetime class AccountDeviceEntry(BaseModel): """Одна строка `devices` в drilldown `GET /audit/accounts/{username}`.""" model_config = ConfigDict(from_attributes=True) user_agent: str | None = None event_count: int first_seen: datetime last_seen: datetime class AccountSearchEntry(BaseModel): """Одна строка `searches` в drilldown — недавний estimate_request.""" model_config = ConfigDict(from_attributes=True) address: str | None = None area_m2: str | None = None rooms: str | None = None estimate_id: str | None = None ip_address: str | None = None created_at: datetime class AccountActivityEntry(BaseModel): """Одна строка `recent_activity` в drilldown — сырое событие.""" model_config = ConfigDict(from_attributes=True) event_type: str path: str | None = None method: str | None = None ip_address: str | None = None created_at: datetime class AccountDrilldown(BaseModel): """Полный ответ `GET /audit/accounts/{username}` — 4 списка. Неизвестный username НЕ 404 — это просто пустой отчёт (все списки == []). """ ips: list[AccountIpEntry] devices: list[AccountDeviceEntry] searches: list[AccountSearchEntry] recent_activity: list[AccountActivityEntry] class AnalyticsSummary(BaseModel): """Верхнеуровневые счётчики дашборда `GET /analytics`.""" model_config = ConfigDict(from_attributes=True) total_events: int distinct_users: int events_last_24h: int active_users_last_24h: int class AnalyticsDailyPoint(BaseModel): """Одна точка time-series `daily` — события/пользователи за день.""" model_config = ConfigDict(from_attributes=True) day: date events: int users: int class AnalyticsTopSearch(BaseModel): """Одна строка `top_searches` — самый частый искомый адрес.""" model_config = ConfigDict(from_attributes=True) address: str | None = None n: int class AnalyticsTopPath(BaseModel): """Одна строка `top_paths` — самый частый API-путь.""" model_config = ConfigDict(from_attributes=True) path: str | None = None n: int class AnalyticsByAccount(BaseModel): """Одна строка `by_account` — сводка активности по username.""" model_config = ConfigDict(from_attributes=True) username: str events: int searches: int last_seen: datetime class AnalyticsDashboard(BaseModel): """Полный ответ `GET /analytics` — бандл для дашборда Feature 3.""" summary: AnalyticsSummary daily: list[AnalyticsDailyPoint] top_searches: list[AnalyticsTopSearch] top_paths: list[AnalyticsTopPath] by_account: list[AnalyticsByAccount]