from typing import Literal, NamedTuple from pydantic import AwareDatetime, BaseModel from resonance_engine.dsp.enums import DSPClientName, DSPId from resonance_engine.fandata.enums import FanCollectionError, FanConnectionStatus CollectionGranularity = Literal["hourly", "daily"] class ActivityBucket(NamedTuple): bucket: str dsp_id: DSPId success: int errors: int throttled: int class ConnectionStats(NamedTuple): total: int active: int revoked: int class CollectionStats(NamedTuple): total: int healthy: int with_errors: int class FanView(BaseModel): fan_id: str dsp_id: DSPId status: FanConnectionStatus first_seen_at: AwareDatetime last_collected_at: AwareDatetime | None last_collection_error: FanCollectionError | None consecutive_failures: int dsp_client_id: int dsp_client_name: DSPClientName dsp_client_display_name: str profile_collected_at: AwareDatetime | None top_artists_collected_at: AwareDatetime | None top_tracks_collected_at: AwareDatetime | None recently_played_collected_at: AwareDatetime | None playlists_collected_at: AwareDatetime | None saved_albums_collected_at: AwareDatetime | None saved_tracks_collected_at: AwareDatetime | None followed_artists_collected_at: AwareDatetime | None class FanViewPaginated(BaseModel): items: list[FanView] next_cursor: str | None class FanRecord(BaseModel): fan_id: str token_encrypted: str