"""Logic for retrieving source of streams breakdown.""" import datetime from typing import Any, Mapping import oto.response from ddtrace import tracer from analytics.constants import cache from analytics.constants.parameters import ALL_TIME from analytics.constants.store import ALL_SOURCE_OF_STREAMS_SOURCES from analytics.logic import data_availability from analytics.logic.parallel import parallel from analytics.logic.stores import add_outage_error_to_stores from analytics.queries.sound_recording_streams_breakdown import StreamsBreakdown from analytics.schemas.streams_breakdown import StreamsBreakdownSchema from analytics.utils import store_availability from analytics.utils.cache import cache_in_redis from analytics.validation.schema import schema_dump def _get_breakdown( breakdown, source_of_streams_sources, streams_by_subscription_sources ): return { "source_of_streams": { "active": { "growth_percentage": None, "total": breakdown["streams_active"], "value": breakdown["streams_active"] / breakdown["streams"] if breakdown["streams"] else 0, }, "passive": { "growth_percentage": None, "total": breakdown["streams_passive"], "value": breakdown["streams_passive"] / breakdown["streams"] if breakdown["streams"] else 0, }, "collection": { "growth_percentage": None, "total": breakdown["streams_collection"], "value": breakdown["streams_collection"] / breakdown["streams"] if breakdown["streams"] else 0, }, "unknown": { "growth_percentage": None, "total": breakdown["streams"] - breakdown["streams_active"] - breakdown["streams_passive"] - breakdown["streams_collection"], "value": ( breakdown["streams"] - breakdown["streams_active"] - breakdown["streams_passive"] - breakdown["streams_collection"] ) / breakdown["streams"] if breakdown["streams"] else 0, }, "sources": source_of_streams_sources, }, "streams_by_subscription": { "subscription": { "growth_percentage": None, "total": breakdown["subscription"], "value": breakdown["subscription"] / breakdown["streams"] if breakdown["streams"] else 0, }, "ad_supported": { "growth_percentage": None, "total": breakdown["ad_supported"], "value": breakdown["ad_supported"] / breakdown["streams"] if breakdown["streams"] else 0, }, "mid_tier": { "growth_percentage": None, "total": breakdown["mid_tier"], "value": breakdown["mid_tier"] / breakdown["streams"] if breakdown["streams"] else 0, }, "sources": streams_by_subscription_sources, }, } def _fetch_breakdown(query_input): """Execute the breakdown query and return the single aggregated row as a dict.""" for row in StreamsBreakdown(query_input).execute(): return dict(row._mapping) if hasattr(row, "_mapping") else dict(row) return None @cache_in_redis(ttl=cache.ONE_DAY) @tracer.wrap(name="get_streams_breakdown") def get_streams_breakdown( query_params: Mapping[str, Any], permissions: Mapping[str, Any], ): """Return subscription type breakdown for a sound recording. Args: query_params: Dict with isrc, distributors, country_ids, store_ids, start_date, end_date. permissions: Dict with permission_* keys. Returns: oto.response.Response: SOS breakdown """ isrc = query_params["isrc"] start_date = query_params.get("start_date") end_date = query_params.get("end_date") if not (start_date and end_date): start_date, end_date = data_availability.get_date_range( data_availability.HIGHWATERMARK_DATE, days=7 ) all_time = start_date == ALL_TIME store_ids = query_params.get("store_ids", []) if not store_ids: store_ids = store_availability.get_store_ids() else: store_ids = sorted( list(set(store_ids).intersection(store_availability.get_store_ids())) ) schema = StreamsBreakdownSchema() def _build_response_body( source_of_streams_sources, streams_by_subscription_sources ): return { "isrc": isrc, "source_of_streams": { "active": {}, "passive": {}, "collection": {}, "unknown": {}, "sources": source_of_streams_sources, }, "streams_by_subscription": { "subscription": {}, "ad_supported": {}, "mid_tier": {}, "sources": streams_by_subscription_sources, }, } if not store_ids: source_of_streams_sources = add_outage_error_to_stores( ALL_SOURCE_OF_STREAMS_SOURCES ) streams_by_subscription_sources = add_outage_error_to_stores( store_availability.get_sources() ) return oto.response.Response( schema_dump( schema, _build_response_body( source_of_streams_sources, streams_by_subscription_sources ), ) ) end_date_str = ( end_date.strftime("%Y-%m-%d") if isinstance(end_date, datetime.date) else end_date ) query_input = { **permissions, "isrc": isrc, "store_ids": store_ids, "distributors": query_params["distributors"], "end_date": end_date_str, "country_ids": query_params.get("country_ids", []), "all_time": all_time, "transfer_product_ownership_enabled": query_params.get( "transfer_product_ownership_enabled", False ), "line_soundcloud_collection_as_active_enabled": query_params.get( "line_soundcloud_collection_as_active_enabled", False ), } if not all_time: start_date_str = ( start_date.strftime("%Y-%m-%d") if isinstance(start_date, datetime.date) else start_date ) query_input["start_date"] = start_date_str requests = { "streams_breakdown": { "func": _fetch_breakdown, "args": (query_input,), }, "source_of_streams_sources": { "func": add_outage_error_to_stores, "args": (ALL_SOURCE_OF_STREAMS_SOURCES,), }, "streams_by_subscription_sources": { "func": add_outage_error_to_stores, "args": (store_availability.get_sources(),), }, } result = parallel(requests) breakdown = result.message["streams_breakdown"] source_of_streams_sources = result.message["source_of_streams_sources"] streams_by_subscription_sources = result.message["streams_by_subscription_sources"] response_body = _build_response_body( source_of_streams_sources, streams_by_subscription_sources ) if breakdown is None: return oto.response.Response(schema_dump(schema, response_body)) sst_breakdown = _get_breakdown( breakdown, source_of_streams_sources, streams_by_subscription_sources ) return oto.response.Response( schema_dump(schema, {**response_body, **sst_breakdown}) )