from collections import defaultdict from dataclasses import dataclass from datetime import datetime from typing import List, Dict, DefaultDict, Optional from external_api.base.available_providers import StreamingProvider from utils.list_utils import group_by, flattern, safe_sum from services.territory.constants import WORLDWIDE @dataclass class DailyStreams: date: str streams: Optional[int] def datetime(self) -> datetime: return datetime.strptime(self.date, "%Y-%m-%d") @dataclass class StreamingStats: countryCode: str data: List[DailyStreams] @dataclass class TrackStreamsQuery: isrc: str start_date: str end_date: str @dataclass class ArtistsQuery: limit: int query: Optional[str] page: Optional[int] = None @dataclass class LinkfireQuery: query: Optional[str] limit: int @dataclass class ArtistFollowersQuery: artist_id: str start_date: str end_date: str @dataclass class ArtistFollowersResult: date: str followers: Optional[int] = None appleFollowers: Optional[int] = None facebookLikes: Optional[int] = None facebookStorytellers: Optional[int] = None instagramFollowers: Optional[int] = None spotifyFollowers: Optional[int] = None spotifyPopularity: Optional[int] = None twitterFollowers: Optional[int] = None youtubeChannelSubscribers: Optional[int] = None youtubeChannelViews: Optional[int] = None def __add__(self, other): return ArtistFollowersResult( date=self.date, followers=safe_sum([self.followers, other.followers]), appleFollowers=safe_sum([self.appleFollowers, other.appleFollowers]), facebookLikes=safe_sum([self.facebookLikes, other.facebookLikes]), facebookStorytellers=safe_sum([self.facebookStorytellers, other.facebookStorytellers]), instagramFollowers=safe_sum([self.instagramFollowers, other.instagramFollowers]), spotifyFollowers=safe_sum([self.spotifyFollowers, other.spotifyFollowers]), spotifyPopularity=safe_sum([self.spotifyPopularity, other.spotifyPopularity]), twitterFollowers=safe_sum([self.twitterFollowers, other.twitterFollowers]), youtubeChannelSubscribers=safe_sum([self.youtubeChannelSubscribers, other.youtubeChannelSubscribers]), youtubeChannelViews=safe_sum([self.youtubeChannelViews, other.youtubeChannelViews]), ) @dataclass class StreamsByDayQuery: start_date: str end_date: str country_code: Optional[str] = WORLDWIDE isrc: Optional[List[str]] = None @dataclass class ProjectStreamsQuery: start_date: str end_date: str project_number: str country_code: Optional[str] = WORLDWIDE @dataclass class StreamsData: streams: Optional[int] leanBack: Optional[int] leanForward: Optional[int] def __add__(self, other): return StreamsData( streams=safe_sum([self.streams, other.streams]), leanBack=safe_sum([self.leanBack, other.leanBack]), leanForward=safe_sum([self.leanForward, other.leanForward]), ) @dataclass class StreamsByDayResult(StreamsData): date: str amazon: StreamsData apple: StreamsData spotify: StreamsData def __add__(self, other): return StreamsByDayResult( date=self.date, streams=safe_sum([self.streams, other.streams]), leanBack=safe_sum([self.leanBack, other.leanBack]), leanForward=safe_sum([self.leanForward, other.leanForward]), amazon=self.amazon + other.amazon, apple=self.apple + other.amazon, spotify=self.spotify + other.spotify, ) @dataclass class TrackStreamsResult: provider: StreamingProvider streams: List[StreamingStats] @dataclass class StreamsAnalyticsResult: results: List[TrackStreamsResult] def merge_daily_streams(streams: List[DailyStreams]) -> List[DailyStreams]: streams_by_date: DefaultDict[str, int] = defaultdict(int) for ds in streams: streams_by_date[ds.date] += ds.streams daily_streams = [DailyStreams(date=date, streams=streams) for date, streams in streams_by_date.items()] return sorted(daily_streams, key=lambda k: k.datetime()) def merge_provider_results(provider: str, results: List[TrackStreamsResult]) -> TrackStreamsResult: stats_by_region: DefaultDict[str, List[DailyStreams]] = defaultdict(list) for result in results: for streaming_data in result.streams: stats_by_region[streaming_data.countryCode].extend(streaming_data.data) streaming_stats: List[StreamingStats] = [] for countryCode, stats in stats_by_region.items(): daily_streams = merge_daily_streams(stats) streaming_stats.append(StreamingStats(countryCode=countryCode, data=daily_streams)) streaming_stats = sorted(streaming_stats, key=lambda x: x.countryCode) return TrackStreamsResult(provider=StreamingProvider[provider], streams=streaming_stats) def merge_streams_analytics_results(results: List[StreamsAnalyticsResult]) -> StreamsAnalyticsResult: grouped_results = group_results_by_provider(results) analytics_results = merge_grouped_analytics(grouped_results) return StreamsAnalyticsResult(results=analytics_results) def merge_grouped_analytics(grouped_results): analytics_results: List[TrackStreamsResult] = [] for provider, results in grouped_results.items(): analytics_results.append(merge_provider_results(provider, results)) return analytics_results def group_results_by_provider(results: List[StreamsAnalyticsResult]) -> Dict[str, List[TrackStreamsResult]]: stream_results = flattern(list(map(lambda r: r.results, results))) return group_by(stream_results, lambda r: r.provider.name)