"""Utils and helpers related to downloads.""" import datetime import itertools from typing import Dict, List from analytics.constants.parameters import ALL_TIME from analytics.utils import store_availability DOWNLOADS_ALL_FIELDS = ["date", "downloads"] DOWNLOADS_BY_COUNTRY_FIELDS = ["country_code", "date", "downloads"] DOWNLOADS_BY_STORE_FIELDS = ["store_id", "date", "downloads"] def get_empty_downloads_for_period(start_period, end_period) -> List[Dict]: """Get list of empty downloads for given date range. Args: start_period (dict): Dictionary representing start period with keys start_date (datetime.date): Start date end_date (datetime.date): End date end_period (dict): Dictionary representing end period with keys identical to start_period above Returns: List of dictionaries with keys date (str): Date in YYYY-MM-DD format downloads (None): Total downloads """ start_date = start_period["start_date"] end_date = end_period["end_date"] empty_downloads = [] for n in range((end_date - start_date).days + 1): empty_downloads.append( { "date": (start_date + datetime.timedelta(n)).strftime("%Y-%m-%d"), "downloads": 0, } ) return empty_downloads def _get_all_time_start_date(download_records, current_start_date): """Get the earliest date available when ALL_TIME start date is submitted. Args: download_records (list): List of dicts representing downloads with values date (datetime.date): Date of downloads downloads (int): Total downloads for date start_date (datetime.date | str): Start date or ALL_TIME keyword end_date (datetime.date): End date current_start_date (datetime.date) Returns: start_date (datetime.date): Date of downloads """ if current_start_date != ALL_TIME: return current_start_date start_date = min(download_records, key=lambda x: x["date"])["date"] return start_date def get_downloads_for_store(download_records, store_id, start_period, end_period): """Get downloads for store for given date range. Args: download_records (list): List of dicts representing download values store_id (int): Store ID date (datetime.date): Date of downloads downloads (int): Total downloads for date store_id (int): Store to get downloads for start_period (dict): Dictionary representing start period with keys start_date (datetime.date): Start date end_date (datetime.date): End date end_period (dict): Dictionary representing end period with keys identical to start_period above Returns: List of dictionaries with keys date (str): Date in YYYY-MM-DD format downloads (int | None): Total downloads for date """ start_period["start_date"] = _get_all_time_start_date( download_records, start_period["start_date"] ) downloads = get_empty_downloads_for_period(start_period, end_period) for record in download_records: if record["store_id"] == store_id: date_idx = (record["date"] - start_period["start_date"]).days if 0 <= date_idx < len(downloads): downloads[date_idx]["downloads"] = record["downloads"] return downloads def get_downloads_by_store(downloads, time_period): """Format downloads by store. Args: downloads (list): downloads result time_period (dict): time period Returns: Formatted downloads by store """ if len(downloads) == 0: return downloads time_period["start_date"] = _get_all_time_start_date( downloads, time_period["start_date"] ) store_names = store_availability.get_download_store_names() all_store_ids = list(set([record["store_id"] for record in downloads])) active_store_ids = set(filter(lambda e: e in store_names, all_store_ids)) downloads_by_store = [] for store_id in active_store_ids: items = get_downloads_for_store(downloads, store_id, time_period, time_period) total_downloads = sum(list(map(lambda item: item["downloads"], items))) downloads_by_store.append( { "id": store_id, "name": store_names.get(store_id), "total_downloads": total_downloads, "items": items, } ) return sorted(downloads_by_store, key=lambda store: store["id"]) def get_downloads_for_country(download_records, country_code, start_period, end_period): """Get downloads for country for given date range. Args: download_records (list): List of dicts representing download values country_code (int): country code date (datetime.date): Date of downloads downloads (int): Total downloads for date country_code (int): Country to get downloads for start_period (dict): Dictionary representing start period with keys start_date (datetime.date): Start date end_date (datetime.date): End date end_period (dict): Dictionary representing end period with keys identical to start_period above Returns: List of dictionaries with keys date (str): Date in YYYY-MM-DD format downloads (int | None): Total downloads for date """ start_period["start_date"] = _get_all_time_start_date( download_records, start_period["start_date"] ) downloads = get_empty_downloads_for_period(start_period, end_period) for record in download_records: record_country_code = record["country_code"] if record_country_code == country_code: date_idx = (record["date"] - start_period["start_date"]).days if 0 <= date_idx < len(downloads): downloads[date_idx]["downloads"] = record["downloads"] if not downloads: return [] return downloads def get_downloads_by_country(downloads, time_period): """Format downloads by country. Args: downloads (list): downloads result time_period (dict): time period Returns: Formatted downloads by country """ if len(downloads) == 0: return downloads time_period["start_date"] = _get_all_time_start_date( downloads, time_period["start_date"] ) country_codes = list(set([record["country_code"] for record in downloads])) downloads_by_country = [] for country_code in country_codes: items = get_downloads_for_country( downloads, country_code, time_period, time_period ) downloads_by_country.append({"code": country_code, "items": items}) return sorted(downloads_by_country, key=lambda country: country["code"]) def get_downloads_totals(downloads, time_period): """Format downloads totals. Args: downloads (list): downloads result time_period (dict): time period Returns: Formatted downloads totals """ downloads_totals_by_date = { k.strftime("%Y-%m-%d"): {"downloads": 0} for k, v in itertools.groupby(downloads, lambda e: e["date"]) } for item in downloads: date_key = item["date"].strftime("%Y-%m-%d") downloads_totals_by_date[date_key]["downloads"] += item["downloads"] time_period["start_date"] = _get_all_time_start_date( downloads, time_period["start_date"] ) downloads_totals = get_empty_downloads_for_period(time_period, time_period) for item in downloads_totals: item["downloads"] = ( downloads_totals_by_date.get(item["date"])["downloads"] if downloads_totals_by_date.get(item["date"]) else 0 ) return downloads_totals def get_downloads_for_track(download_records, tuid, start_period, end_period): """Get downloads timeseries for given tuid for given date range. Args: download_records (list): List of dicts representing downloads tuid (int): Tuid start_date (datetime.date): Start date end_date (datetime.date): End date Returns: List of dicts with keys track_unique_id (int): tuid items (list): List of dicts representing streams with values date (str): Date in YYYY-MM-DD format downloads (int): Total downloads for date """ result = {"track_unique_id": None, "items": []} start_period["start_date"] = _get_all_time_start_date( download_records, start_period["start_date"] ) downloads = get_empty_downloads_for_period(start_period, end_period) for record in download_records: if record["track_unique_id"] == tuid: result["track_unique_id"] = record["track_unique_id"] date_idx = (record["date"] - start_period["start_date"]).days if 0 <= date_idx < len(downloads): downloads[date_idx]["downloads"] = record["downloads"] result["items"] = downloads return result def get_downloads_by_track(downloads, time_period): """Format downloads by track timeseries for given date range. Args: downloads (list): List of dicts representing downloads with values track_unique_id (int): tuid date (datetime.date): Date of streams downloads (int): Total downloads for date time_period (dict): time period Return: Formatted downloads by track timeseries """ if len(downloads) == 0: return downloads time_period["start_date"] = _get_all_time_start_date( downloads, time_period["start_date"] ) all_tuids = list(set([record["track_unique_id"] for record in downloads])) downloads_by_track = [] for tuid in all_tuids: downloads_for_track = get_downloads_for_track( downloads, tuid, time_period, time_period ) downloads_by_track.append(downloads_for_track) return downloads_by_track def get_downloads_for_sound_recording(download_records, isrc, start_date, end_date): """Get downloads timeseries for given isrc for given date range. Args: download_records (list): List of dicts representing downloads isrc (str): ISRC of sound_recording date (datetime.date): Date of downloads downloads (int): Total downloads for date isrc (str): ISRC of sound_recording start_date (datetime.date): Start date end_date (datetime.date): End date Returns: List of dictionaries with keys: date (str): Date in YYYY-MM-DD format downloads (int | None): Total downloads for date saves (int | None): Total saves for date skip_rate (float | None): Total skip rate for date """ start_date = _get_all_time_start_date(download_records, start_date) downloads = get_empty_downloads_for_period( {"start_date": start_date}, {"end_date": end_date} ) for record in download_records: if record["isrc"] == isrc: date_idx = (record["date"] - start_date).days if 0 <= date_idx < len(downloads): downloads[date_idx]["downloads"] = record["downloads"] return downloads def get_downloads_by_sound_recording(downloads, start_date, end_date): """Get downloads by sound recording timeseries for given date range. Args: downloads (list): List of dicts representing downloads with values isrc (str): ISRC of sound recording date (datetime.date): Date of downloads downloads (int): Total downloads for date start_date (datetime.date | str): Start date or ALL_TIME keyword end_date (datetime.date): End date Return: Formatted downloads by isrc timeseries """ if not downloads: return None start_date = _get_all_time_start_date(downloads, start_date) all_sound_recordings = list(set([record["isrc"] for record in downloads])) downloads_by_sound_recording = [] for isrc in all_sound_recordings: items = get_downloads_for_sound_recording(downloads, isrc, start_date, end_date) downloads_by_sound_recording.append({"isrc": isrc, "items": items}) return downloads_by_sound_recording