from datetime import date from typing import List, Optional from operator import mul from external_api.base.clients.delphi_client import AdsPerformanceResponse, AdPerformanceSummaryItem from utils.date_utils import dates_between, compare_dates from utils.list_utils import get_nested_attr, index_of, safe_sum from external_api.base.analytics_models import StreamsByDayResult, ArtistFollowersResult from reporting.services.reporting_metrics.combined_metrics import CombinedMetrics from models import StreamRate def safe_mul(value: float, multiplicator: float) -> float: if not isinstance(value, type(None)) and not isinstance(multiplicator, type(None)): return mul(value, multiplicator) return 0.0 class CombinedMetricsBuilder: streams_data: List[StreamsByDayResult] = [] followers_data: List[ArtistFollowersResult] = [] facebook_metrics: Optional[AdsPerformanceResponse] google_metrics: Optional[AdsPerformanceResponse] linkfire_metrics: Optional[AdsPerformanceResponse] revenue_stream_rate: Optional[StreamRate] def build( self, start_date: date, end_date: date, revenue_stream_rate: Optional[StreamRate] = None ) -> List[CombinedMetrics]: self.revenue_stream_rate = revenue_stream_rate result = [] for _date in dates_between(start_date, end_date): combined_metrics = CombinedMetrics(date=_date) self.__merge_followers(combined_metrics) self.__merge_streams(combined_metrics) self.__merge_platform_metrics(combined_metrics) result.append(combined_metrics) return result def __merge_followers(self, target: CombinedMetrics): followers: Optional[ArtistFollowersResult] = None if self.followers_data: followers = self.__pop_by_date(self.followers_data, target.date) target.followers = getattr(followers, "followers", None) target.facebookLikes = getattr(followers, "facebookLikes", None) target.instagramFollowers = getattr(followers, "instagramFollowers", None) target.spotifyFollowers = getattr(followers, "spotifyFollowers", None) target.twitterFollowers = getattr(followers, "twitterFollowers", None) target.appleFollowers = getattr(followers, "appleFollowers", None) target.youtubeChannelSubscribers = getattr(followers, "youtubeChannelSubscribers", None) def __merge_streams(self, target: CombinedMetrics): streams: Optional[StreamsByDayResult] = None if self.streams_data: streams = self.__pop_by_date(self.streams_data, target.date) target.streams = getattr(streams, "streams", None) target.leanBackStreams = getattr(streams, "leanBack", None) target.leanForwardStreams = getattr(streams, "leanForward", None) target.amazonStreams = get_nested_attr(streams, "amazon.streams", None) target.amazonLeanBackStreams = get_nested_attr(streams, "amazon.leanBack", None) target.amazonLeanForwardStreams = get_nested_attr(streams, "amazon.leanForward", None) target.appleStreams = get_nested_attr(streams, "apple.streams", None) target.appleLeanBackStreams = get_nested_attr(streams, "apple.leanBack", None) target.appleLeanForwardStreams = get_nested_attr(streams, "apple.leanForward", None) target.spotifyStreams = get_nested_attr(streams, "spotify.streams", None) target.spotifyLeanBackStreams = get_nested_attr(streams, "spotify.leanBack", None) target.spotifyLeanForwardStreams = get_nested_attr(streams, "spotify.leanForward", None) target.revenueStreams = safe_mul( getattr(streams, "streams", 0), getattr(self.revenue_stream_rate, "cross_platform_blended_rate", 0) ) target.revenueAmazonStreams = safe_mul( get_nested_attr(streams, "amazon.streams", 0), getattr(self.revenue_stream_rate, "spotify_blended_rate", 0) ) target.revenueAppleStreams = safe_mul( get_nested_attr(streams, "apple.streams", 0), getattr(self.revenue_stream_rate, "apple_blended_rate", 0) ) target.revenueSpotifyStreams = safe_mul( get_nested_attr(streams, "spotify.streams", 0), getattr(self.revenue_stream_rate, "spotify_blended_rate", 0) ) def __merge_platform_metrics(self, target: CombinedMetrics): facebook_data: Optional[AdPerformanceSummaryItem] = None google_data: Optional[AdPerformanceSummaryItem] = None linkfire_data: Optional[AdPerformanceSummaryItem] = None if self.facebook_metrics: facebook_data = self.__pop_by_date( self.facebook_metrics.items, target.date, date_attr_path="dimensions.date" ) if self.google_metrics: google_data = self.__pop_by_date(self.google_metrics.items, target.date, date_attr_path="dimensions.date") if self.linkfire_metrics: linkfire_data = self.__pop_by_date( self.linkfire_metrics.items, target.date, date_attr_path="dimensions.date" ) if facebook_data is None and google_data is None and linkfire_data is None: return # SPEND target.facebookSpend = get_nested_attr(facebook_data, "metrics.finance.spend", None) target.googleSpend = get_nested_attr(google_data, "metrics.finance.spend", None) target.spend = safe_sum([target.facebookSpend, target.googleSpend]) # VIDEO VIEWS target.facebookThruplays = get_nested_attr(facebook_data, "metrics.video.thruplays", None) target.facebookVideoViews = get_nested_attr(facebook_data, "metrics.video.video_views", None) target.googleVideoViews = get_nested_attr(google_data, "metrics.video.video_views", None) target.videoViews = safe_sum([target.facebookVideoViews, target.googleVideoViews]) # IMPRESSIONS target.facebookImpressions = get_nested_attr(facebook_data, "metrics.general.impressions", None) target.googleImpressions = get_nested_attr(google_data, "metrics.general.impressions", None) target.impressions = safe_sum([target.facebookImpressions, target.googleImpressions]) # CONVERSIONS target.conversions = safe_sum( [ get_nested_attr(facebook_data, "metrics.general.conversions", None), get_nested_attr(google_data, "metrics.general.conversions", None), ] ) # CLICKS target.facebookClicks = get_nested_attr(facebook_data, "metrics.general.clicks", None) target.googleClicks = get_nested_attr(google_data, "metrics.general.clicks", None) target.clicks = safe_sum([target.facebookClicks, target.googleClicks]) # REACH target.reach = safe_sum( [ get_nested_attr(facebook_data, "metrics.general.reach", None), get_nested_attr(google_data, "metrics.general.reach", None), ] ) # ENGAGEMENT target.facebookReactions = get_nested_attr(facebook_data, "metrics.general.reactions", None) target.googleEngagements = get_nested_attr(google_data, "metrics.general.engagements", None) target.facebookComments = get_nested_attr(facebook_data, "metrics.general.comments", None) target.engagements = safe_sum( [get_nested_attr(facebook_data, "metrics.general.engagements", None), target.googleEngagements] ) # CPM target.facebookCpm = self.__calculate_cpm(target.facebookSpend, target.facebookImpressions) target.googleCpm = self.__calculate_cpm(target.googleSpend, target.googleImpressions) target.cpm = self.__calculate_cpm(target.spend, target.impressions) # Linkfire target.linkfireClicks = get_nested_attr(linkfire_data, "metrics.linkfire.clicks", None) target.linkfireEvents = get_nested_attr(linkfire_data, "metrics.linkfire.events", None) target.linkfirePageViews = get_nested_attr(linkfire_data, "metrics.linkfire.page_views", None) def __calculate_cpm(self, spend: Optional[float], impressions: Optional[int]) -> Optional[float]: if spend is None or impressions is None or impressions == 0: return None return (spend * 1000) / impressions def __pop_by_date(self, array, _date: date, date_attr_path: Optional[str] = "date"): try: return array.pop(index_of(array, lambda x: compare_dates(get_nested_attr(x, date_attr_path), _date))) except IndexError: return None