from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field MetricName = Literal[ "views", "impressions", "likes", "comments", "shares", "engagement_rate", "reach", "clicks", "saves", "watch_time", "completion_rate", ] Granularity = Literal["day", "week", "month"] class AnalyticsQuery(BaseModel): model_config = ConfigDict(extra="forbid") date_from: datetime | None = None date_to: datetime | None = None timezone: str = Field(default="UTC", min_length=1, max_length=100) provider: str | None = Field(default=None, max_length=32) project_id: str | None = Field(default=None, max_length=36) social_account_id: str | None = Field(default=None, max_length=36) post_id: str | None = Field(default=None, max_length=36) metric: MetricName | None = None granularity: Granularity = "day" search: str | None = Field(default=None, max_length=200) sort: Literal["date", "views", "engagement_rate", "likes", "comments", "shares"] = "date" descending: bool = True offset: int = Field(default=0, ge=0, le=100_000) limit: int = Field(default=50, ge=1, le=500) class AnalyticsMetric(BaseModel): model_config = ConfigDict(extra="forbid") provider: str post_id: str | None = None project_id: str | None = None metric_date: datetime views: int | None = None impressions: int | None = None likes: int | None = None comments: int | None = None shares: int | None = None engagement_rate: float | None = None status: Literal["available", "unavailable", "unsupported", "not_synchronized"] = "available" source: str = "provider" collected_at: datetime | None = None class AnalyticsFreshness(BaseModel): status: Literal["fresh", "stale", "not_synchronized", "partial", "failed"] last_collected_at: datetime | None = None last_sync_id: str | None = None reason: str | None = None class AnalyticsOverview(BaseModel): model_config = ConfigDict(extra="forbid") date_from: datetime date_to: datetime timezone: str timezone_source: Literal["requested", "utc_fallback"] = "requested" totals: dict[str, int | float | None] platforms: list[dict[str, Any]] top_posts: list[AnalyticsMetric] freshness: AnalyticsFreshness class AnalyticsTimeseries(BaseModel): date_from: datetime date_to: datetime timezone: str granularity: Granularity metric: str points: list[dict[str, Any]] freshness: AnalyticsFreshness class AnalyticsSyncRequest(BaseModel): model_config = ConfigDict(extra="forbid") date_from: datetime | None = None date_to: datetime | None = None timezone: str = Field(default="UTC", min_length=1, max_length=100) provider: str | None = Field(default=None, max_length=32) project_id: str | None = Field(default=None, max_length=36) idempotency_key: str | None = Field(default=None, min_length=8, max_length=255) class AnalyticsSyncRunView(BaseModel): model_config = ConfigDict(from_attributes=True, extra="forbid") id: str status: Literal["queued", "running", "succeeded", "partial", "failed", "cancelled"] provider: str | None = None project_id: str | None = None date_from: datetime date_to: datetime timezone: str attempt_count: int metrics_count: int error_code: str | None = None error_message: str | None = None created_at: datetime started_at: datetime | None = None completed_at: datetime | None = None updated_at: datetime class AnalyticsCapabilities(BaseModel): provider: str implementation_status: str status: Literal["available", "unsupported", "unavailable"] metrics: list[str] required_scopes: list[str] = Field(default_factory=list) class AnalyticsSyncList(BaseModel): items: list[AnalyticsSyncRunView] offset: int limit: int class AnalyticsPostList(BaseModel): items: list[AnalyticsMetric] offset: int limit: int freshness: AnalyticsFreshness