from __future__ import annotations import uuid from typing import Any from pydantic import AwareDatetime, BaseModel, ConfigDict from app.dsp.enums import DSPId from app.fandata.enums import FanCredentialsStatus from app.pipeline.enums import RunFinishedReason, RunSource, RunStatus class DSPClientInfo(BaseModel): model_config = ConfigDict(from_attributes=True) dsp_id: DSPId name: str display_name: str class PipelineRun(BaseModel): model_config = ConfigDict(from_attributes=True) id: uuid.UUID status: RunStatus dsp_client: DSPClientInfo dsp_client_id: int source: RunSource | None filters: dict[str, Any] | None total_fans: int fans_processed: int total_batches: int completed_batches: int errors: int rate_limited: int stale_tokens: int created_at: AwareDatetime started_at: AwareDatetime | None finished_at: AwareDatetime | None finished_reason: RunFinishedReason | None total_requests: int throughput_fps: float | None = None throughput_rps: float | None = None is_stale: bool = False eta_seconds: int | None = None class PipelineRunPaginated(BaseModel): items: list[PipelineRun] total: int class StartPipelineInput(BaseModel): dsp_client_id: int token_status: FanCredentialsStatus | None = FanCredentialsStatus.active not_collected_since: AwareDatetime | None = None max_consecutive_failures: int | None = None limit: int | None = None class CleanupStaleRunsResponse(BaseModel): cleaned: list[str] count: int class EstimateResponse(BaseModel): total_fans: int estimated_seconds: int | None requests_per_fan: int throughput_rps: int | None class DashboardStats(BaseModel): collected_today: int top_artists_total: int recently_played_total: int throughput: int | None throughput_rps: float | None rate_limited_last_run: int | None stale_tokens_last_run: int | None active_run_id: str | None