"""Geographic insights Logic.""" from datetime import timedelta from dateutil.parser import parse from oto import response as oto_response from analytics.consts import account as account_constants from analytics.models import geographics from analytics.models import ows_account from analytics.utils import parallel def get_geographic_insights( start_date, end_date, isrcs, artist_ids, store_ids, feed_ids, account_type, account_id, distributors): """Get geographics data from model layer. Args: start_date (str): Start date. end_date (str): End date. isrcs (list): List of isrcs. artist_ids (list): List of artist ids. store_ids (list): List of store ids. feed_ids (list): List of available feed ids. account_type (str): Grass account type. account_id (str): Grass account id. distributors (list): List of distributors names. Returns: response.Response: containing the geographics data dict. """ if account_type == account_constants.SUBACCOUNT_TYPE: ows_account_response = ows_account.get_vendor_id_by_subaccount_id( account_id) if not ows_account_response: return ows_account_response subaccount_id = account_id account_id = ows_account_response.message else: subaccount_id = None previous_time_period = _compute_previous_time_period(start_date, end_date) if previous_time_period: this_time_period = TimePeriod(start_date, end_date) return _get_and_compare_current_and_previous_data( previous_time_period, this_time_period, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors) else: result = geographics.get_geographic_insights( start_date, end_date, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors) if result.status != 200: return result return oto_response.Response( message=_calculate_geographic_response(result.message)) def get_geographic_insights_by_region( start_date, end_date, isrcs, artist_ids, store_ids, feed_ids, account_type, account_id, distributors): """Get geographics data by region from model layer. Args: start_date (str): Start date. end_date (str): End date. isrcs (list): List of isrcs. artist_ids (list): List of artist ids. store_ids (list): List of store ids feed_ids (list): List of feed ids account_type (str): Grass account type. account_id (str): Grass account id. distributors (list): List of distributors names. Returns: response.Response: containing the geographics data by region dict. """ if account_type == account_constants.SUBACCOUNT_TYPE: ows_account_response = ows_account.get_vendor_id_by_subaccount_id( account_id) if not ows_account_response: return ows_account_response subaccount_id = account_id account_id = ows_account_response.message else: subaccount_id = None previous_time_period = _compute_previous_time_period(start_date, end_date) if previous_time_period: this_time_period = TimePeriod(start_date, end_date) return _get_and_compare_current_and_previous_data_by_region( previous_time_period, this_time_period, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors) else: result = geographics.get_geographic_insights_by_region( start_date, end_date, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors) if result.status != 200: return result return oto_response.Response( message=_calculate_geographic_response(result.message)) def _get_and_compare_current_and_previous_data( previous_time_period, this_time_period, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors): """Get the data for the current and previous time periods and get % inc/dec. Args: previous_time_period (TimePeriod): Previous time period. this_time_period (TimePeriod): Previous time period. isrcs (list): List of isrcs. artist_ids (list): List of artist ids. store_ids (list): List of store ids account_id (int): user label id subaccountid (int): user subaccount id distributors (list): list of distributors names. Returns: response.Response: containing the geographics data dict. """ results = parallel.execute_in_parallel({ 'geographic_insights': { 'func': geographics.get_geographic_insights, 'args': (this_time_period.start_date, this_time_period.end_date, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors), 'kwargs': {} }, 'previous_geographic_insights': { 'func': geographics.get_geographic_insights, 'args': (previous_time_period.start_date, previous_time_period.end_date, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors), 'kwargs': {} } }) geographic_insights_result = results['geographic_insights'].message previous_geographic_insights_result = results[ 'previous_geographic_insights'].message if geographic_insights_result and previous_geographic_insights_result: geographic_insights_result = _calculate_increase_decrease_percentage( geographic_insights_result, previous_geographic_insights_result, 'territory_code') return oto_response.Response(message=_calculate_geographic_response( geographic_insights_result, previous_geographic_insights_result)) result = geographics.get_geographic_insights( this_time_period.start_date, this_time_period.end_date, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors) if result.status != 200: return result return oto_response.Response( message=_calculate_geographic_response(result.message)) def _calculate_geographic_response( current_geographics_result, previous_geographics_result=None): """Calculate and format the response message for geographic insights. Args: current_geographics_result (list): current geographic insights previous_geographics_result (list): previous geographic insights Returns: dict: streams and listeners totals and growth and list of insights """ total_streams = _calculate_totals(current_geographics_result) payload = { 'total_streams': total_streams, 'total_listeners': 0, 'items': current_geographics_result } if previous_geographics_result: previous_total_streams = _calculate_totals( previous_geographics_result) total_streams_growth = _calculate_percentage( total_streams, previous_total_streams) payload.update({ 'total_streams_growth': total_streams_growth, 'total_listeners_growth': 0}) return payload def _calculate_totals(geographic_insights): """Calculate and return the total streams. Args: geographic_insights (list): List of dictionary of Geographic Insights Returns: total_streams (int): the total streams """ total_streams = sum( [region.get('number_of_streams') for region in geographic_insights]) return total_streams def _get_and_compare_current_and_previous_data_by_region( previous_time_period, this_time_period, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors): """Get the data for the current and previous time periods and get % inc/dec. Args: previous_time_period (TimePeriod): Previous time period. this_time_period (TimePeriod): Previous time period. isrcs (list): List of isrcs. artist_ids (list): List of artist ids. store_ids (list): List of store ids feed_ids (list): List of feed ids account_id (int): user label id subaccountid (int): user subaccount id distributors (list): list of distributors names. Returns: response.Response: containing the geographics data by region dict. """ results = parallel.execute_in_parallel({ 'insights_by_region': { 'func': geographics.get_geographic_insights_by_region, 'args': (this_time_period.start_date, this_time_period.end_date, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors), 'kwargs': {} }, 'previous_insights_by_region': { 'func': geographics.get_geographic_insights_by_region, 'args': (previous_time_period.start_date, previous_time_period.end_date, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors), 'kwargs': {} } }) insights_by_region_result = results['insights_by_region'].message previous_insights_by_region_result = results[ 'previous_insights_by_region'].message if insights_by_region_result and previous_insights_by_region_result: insights_by_region_result = _calculate_increase_decrease_percentage( insights_by_region_result, previous_insights_by_region_result, 'region_code') return oto_response.Response(message=_calculate_geographic_response( insights_by_region_result, previous_insights_by_region_result)) result = geographics.get_geographic_insights_by_region( this_time_period.start_date, this_time_period.end_date, isrcs, artist_ids, store_ids, feed_ids, account_id, subaccount_id, distributors) if result.status != 200: return result return oto_response.Response( message=_calculate_geographic_response(result.message)) def _compute_previous_time_period(start_date, end_date): """Work out what the previous time period is and if there should be one. Args: start_date (str): Start date. end_date (str): End date. Returns: dict: start_date and end_date for previous period if there is one """ start_date_as_date = parse(start_date) end_date_as_date = parse(end_date) date_difference = end_date_as_date - start_date_as_date one_day = timedelta(days=1) if date_difference.days > 30: return None else: return TimePeriod( (start_date_as_date - date_difference - one_day).strftime( '%Y-%m-%d'), (end_date_as_date - date_difference - one_day).strftime( '%Y-%m-%d')) def _calculate_increase_decrease_percentage( current, previous, matching_field_name): """Calculate inc/dec percentage for the current and previous time periods. Args: current (dict): Current insights result. previous (dict): Previous insights result. Returns: (dict): containing insights with growth percentage. """ for current_insight in current: current_streams = current_insight.get('number_of_streams', None) current_code = current_insight.get(matching_field_name, None) if None in [current_code, current_streams]: continue for previous_insight in previous: previous_streams = previous_insight.get('number_of_streams', None) previous_code = previous_insight.get(matching_field_name, None) if None in [previous_code, previous_streams]: continue if current_code != previous_code: continue current_insight['listeners_growth'] = 0 current_insight['streams_growth'] = ( _get_growth_percentage_from_insights( current_insight, previous_insight, 'number_of_streams')) break return current def _get_growth_percentage_from_insights( current_insight, previous_insight, attribute): """Calculate percentage for the current and previous numbers. Args: current_insight (dict): Current insight from which to get growth. previous_insight (dict): Previous insight from which to get growth. attribute (str): Property within dict from which to get growth. Returns: (int): containing the rounded signed percentage number. """ percentage = None current_streams = current_insight.get(attribute, None) previous_streams = previous_insight.get(attribute, None) if previous_streams and current_streams: percentage = _calculate_percentage( current_streams, previous_streams) return percentage def _calculate_percentage(current, previous): """Calculate percentage for the current and previous numbers. Args: current (int): Current number. previous (int): Previous number. Returns: (int): containing the rounded signed percentage number. """ diff = current - previous growth_percentage = diff / previous * 100 return round(growth_percentage, 2) class TimePeriod: """Represents a time period with a start and end date.""" def __init__(self, start_date, end_date): """Initialise a time period.""" self.start_date = start_date self.end_date = end_date def get_geographic_orchard_regions(): """Get geographics orchard regions from model layer. Returns: response.Response: containing the geographic orchard regions dict. """ return geographics.get_geographic_orchard_regions() def get_geographics_latest_date(feed_ids, distributors): """Get geographics orchard latest dates from model layer. feed_ids (list): List of available feeds. distributors (list): List of distributors names. Returns: response.Response: containing the latest available date from summary geographic tables. """ response = geographics.get_geographics_latest_date(feed_ids, distributors) if not response: return response last_day_data = response.message data = {} data['last_activity_date'] = str(last_day_data) return oto_response.Response(message=data)