import uuid from datetime import datetime from pydantic import AwareDatetime, BaseModel, Field, SecretStr from resonance_engine.config import settings from resonance_engine.dsp.enums import DSPClientName, DSPClientStatus, DSPId from resonance_engine.dsp.stats import RequestStats from resonance_engine.fandata.enums import FanConnectionStatus from resonance_engine.fandata.models import FanConnectionFilter from resonance_engine.fandata.types import CollectionGranularity from resonance_engine.tasks.enums import FanoutSource, TaskFinishedReason, TaskStatus # Infra class HelloResponse(BaseModel): status: str = "ok" class AuthConfig(BaseModel): auth_enabled: bool auth0_domain: str auth0_client_id: str auth0_audience: str auth0_callback_url: str auth0_logout_redirect: str auth0_post_login_redirect: str @classmethod def from_settings(cls) -> AuthConfig: return cls( auth_enabled=settings.auth_enabled, auth0_domain=settings.auth0_domain, auth0_client_id=settings.auth0_client_id, auth0_audience=settings.auth0_audience, auth0_callback_url=settings.auth0_callback_url, auth0_logout_redirect=settings.auth0_logout_redirect, auth0_post_login_redirect=settings.auth0_post_login_redirect, ) # Config class AppConfig(BaseModel): environment: str service_name: str service_version: str class AwsConfig(BaseModel): region_name: str class DsqlConfig(BaseModel): endpoint: str | None token_expires_in: int class EncryptionConfig(BaseModel): key_ids: list[str] active_key_id: str | None keys_secret_name: str class DspConfig(BaseModel): spotify_songwhip_client_id: str spotify_songwhip_client_secret_name: str spotify_smf_sme_client_id: str spotify_smf_sme_client_secret_name: str spotify_smf_orch_client_id: str spotify_smf_orch_client_secret_name: str class KafkaTopicsConfig(BaseModel): profile: str top_artists: str top_tracks: str recently_played: str playlists: str saved_albums: str saved_tracks: str followed_artists: str class KafkaConfig(BaseModel): bootstrap_servers: str use_ssl: bool topics: KafkaTopicsConfig class SqsConfig(BaseModel): fan_collect_spotify_songwhip_queue_url: str fan_collect_spotify_smf_sme_queue_url: str fan_collect_spotify_smf_orch_queue_url: str fan_collect_deezer_songwhip_queue_url: str fan_collect_amazon_songwhip_queue_url: str fan_collect_apple_songwhip_queue_url: str class FanCollectIntervalsConfig(BaseModel): profile: int top_artists: int top_tracks: int recently_played: int playlists: int saved_albums: int saved_tracks: int followed_artists: int class FanCollectConfig(BaseModel): worker_timeout_s: int token_error_revoke_threshold: int intervals: FanCollectIntervalsConfig class FanFanoutConfig(BaseModel): window_s: int async_enabled: bool class ConfigResponse(BaseModel): app: AppConfig aws: AwsConfig dsql: DsqlConfig kafka: KafkaConfig sqs: SqsConfig encryption: EncryptionConfig dsp: DspConfig auth: AuthConfig fan_collect: FanCollectConfig fan_fanout: FanFanoutConfig # DSP class UpdateDSPInput(BaseModel): nominal_rps: int | None class DSPClient(BaseModel): id: int name: DSPClientName display_name: str status: DSPClientStatus configured: bool stats: RequestStats | None nominal_rps: int | None class DSP(BaseModel): id: DSPId clients: list[DSPClient] class RecommendedConcurrency(BaseModel): recommended: int nominal_rps: int | None observed_rps: float | None latency_s: float throttle_rate: float basis: str # "cold" | "grow" | "backoff" ran: float | None # observed_rps * latency_s (Little's law) — None at cold start # Fan data class FanConnectionStats(BaseModel): total: int active: int revoked: int class FanCollectionStats(BaseModel): total: int healthy: int with_errors: int class GetFanStatsInput(BaseModel): dsp_client_name: DSPClientName | None = None class FanStats(BaseModel): connections: FanConnectionStats collection: FanCollectionStats approximate: bool = True class CreateFanConnectionInput(BaseModel): email: str dsp_client_name: DSPClientName token: SecretStr class BatchCreateFanConnectionInput(BaseModel): items: list[CreateFanConnectionInput] = Field(max_length=1000) class BatchCreateResult(BaseModel): count: int class FanConnection(BaseModel): fan_id: str dsp_id: DSPId dsp_client_id: int status: FanConnectionStatus token_refreshed_at: datetime | None created_at: datetime class GetFansInput(BaseModel): dsp_id: DSPId | None = None dsp_client_names: list[DSPClientName] | None = None status: FanConnectionStatus | None = None search: str | None = None is_collected: bool | None = None cursor: str | None = None limit: int = 50 class CollectFanConnectionInput(BaseModel): fan_id: str dsp_client_name: DSPClientName force: bool = False # Tasks class GetFanoutTasksInput(BaseModel): cursor: str | None = None limit: int = Field(20, ge=1, le=100) status: TaskStatus | None = None source: FanoutSource | None = None dsp_client_name: DSPClientName | None = None class FanoutRunStats(BaseModel): total: int running: int last_started_at: AwareDatetime | None last_fans_dispatched: int | None class CollectRunStats(BaseModel): total: int running: int stale: int last_started_at: AwareDatetime | None class GetTaskStatsInput(BaseModel): dsp_client_name: DSPClientName | None = None class TaskStats(BaseModel): fanout: FanoutRunStats collect: CollectRunStats class GetTaskActivityInput(BaseModel): days: int = 7 granularity: CollectionGranularity = "daily" dsp_client_name: DSPClientName | None = None class TaskActivityBucket(BaseModel): bucket: str dsp_client_name: str dsp_client_display_name: str fans_processed: int fans_errors: int fans_stale_tokens: int requests_rate_limited: int requests: int class FanoutTask(BaseModel): id: uuid.UUID started_at: AwareDatetime finished_at: AwareDatetime | None source: FanoutSource status: TaskStatus finished_reason: TaskFinishedReason | None fans_dispatched: int messages_sent: int filters: FanConnectionFilter fans_collected: int = 0 fans_errors: int = 0 fans_stale_tokens: int = 0 fans_skipped: int = 0 requests: int = 0 requests_rate_limited: int = 0 collect_tasks_done: int = 0 class FanoutTaskPaginated(BaseModel): items: list[FanoutTask] next_cursor: str | None total: int class CollectTask(BaseModel): id: uuid.UUID fanout_task_id: uuid.UUID dsp_client_name: DSPClientName started_at: AwareDatetime status: TaskStatus finished_at: AwareDatetime | None finished_reason: TaskFinishedReason | None fans_total: int fans_skipped: int fans_processed: int fans_errors: int fans_stale_tokens: int requests: int requests_rate_limited: int requests_skipped: int