from flask import jsonify, request from analytics import config from analytics.api import app from analytics.constants.access import ACCESS_ANALYTICS from analytics.features import ( is_insights_line_soundcloud_collection_as_active_enabled, is_insights_published_max_available_date_enabled, is_insights_transfer_product_ownership_enabled, ) from analytics.handler_utils import _get_query_string_params, user_has_full_access from analytics.logic.account_products import get_products from analytics.logic.account_timeseries import ( get_account_summary, get_account_timeseries, get_account_video_summary, get_account_video_timeseries, ) from analytics.logic.account_top_content import get_top_content from analytics.logic.permissions import get_permission_values from analytics.validation.access import verify_profile @app.route(config.ACCOUNT_METRICS_TIME_SERIES_PATH) @verify_profile(access=ACCESS_ANALYTICS) def account_timeseries(account_id): """Return timeseries for an account. Route: GET /account//timeseries Path params: account_id (str): Account ID. Query params: start_date (str): Start date YYYY-MM-DD. end_date (str): End date YYYY-MM-DD. type (str): Timeseries type. Values: ACCOUNT_STREAMS, ACCOUNT_STREAMS_BY_COUNTRY, ACCOUNT_STREAMS_BY_STORE, ACCOUNT_STREAMS_BY_TRACK, ACCOUNT_STREAMS_BY_PRODUCT, ACCOUNT_STREAMS_BY_IMPRINT, ACCOUNT_STREAMS_BY_PROJECT, ACCOUNT_STREAMS_BY_SUBACCOUNT, ACCOUNT_STREAMS_BY_PARTICIPANT, ACCOUNT_STREAMS_BY_SOUND_RECORDING_FAMILY, ACCOUNT_STREAMS_BY_SUBSCRIPTION, ACCOUNT_STREAMS_BY_SOS, ACCOUNT_ALBUM_DOWNLOADS, ACCOUNT_ALBUM_DOWNLOADS_BY_*, ACCOUNT_TRACK_DOWNLOADS, ACCOUNT_TRACK_DOWNLOADS_BY_*. resolution (str): Time resolution. account_type (str): Account type (vendor/subaccount). countries (list[str]): Country codes to filter by. subscription_types (list[str]): Subscription types. distributors (list[str]): Distributor names. fin_label_ids (list[str]): Financial label IDs. upper_profit_centers (list[str]): Upper profit center IDs. store_ids (list[int]): Store IDs to filter by. ids (list[str]): IDs to filter aggregation by. Snowflake tables: - V_STREAMS_BY_ACCOUNT_TRACK_FEED_DISTRIBUTOR_DAILY - V_STREAMS_BY_ACCOUNT_TRACK_COUNTRY_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_COUNTRY_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_TRACK_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_TRACK_COUNTRY_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_TRACK_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_TRACK_COUNTRY_FEED_DISTRIBUTOR_DAILY JOINs: - DIM_RELEASE - DIM_TRACK_CLEAN_MV - PROJECT - MAPPINGS_PRODUCT_TO_PRIMARY_GLOBAL_PARTICIPANT - MAPPINGS_PRODUCT_FAMILY_TO_ISRCS - MAPPINGS_FINANCIAL_LABEL_ID_TO_PRODUCT_IDS - MAPPINGS_UPPER_PROFIT_CENTER_TO_PRODUCT_IDS - DIM_SUBACCOUNT Returns: JSON with timeseries items. """ path_params = {"account_id": account_id} query_params = _get_query_string_params( ["start_date", "end_date", "type", "resolution"], path_params ) query_params = { **query_params, "account_type": request.args.get("account_type"), "countries": request.args.getlist("countries"), "subscription_types": request.args.getlist("subscription_types"), "distributors": request.args.getlist("distributors"), "fin_label_ids": request.args.getlist("fin_label_ids"), "upper_profit_center_ids": request.args.getlist("upper_profit_centers"), "store_ids": request.args.getlist("store_ids"), "ids": request.args.getlist("ids"), "transfer_product_ownership_enabled": is_insights_transfer_product_ownership_enabled(), "line_soundcloud_collection_as_active_enabled": is_insights_line_soundcloud_collection_as_active_enabled(), } permissions = get_permission_values() # do not return data for a vendor if a user has permissions only to subaccount if ( query_params["account_type"] == "vendor" and permissions["permission_subaccount_ids"] and not permissions["permission_label_ids"] and not user_has_full_access(permissions) ): return jsonify({"items": []}), 200 timeseries = get_account_timeseries(query_params, permissions) response_body = {"items": timeseries} return jsonify(response_body), 200 @app.route(config.ACCOUNT_METRICS_SUMMARY_PATH) @verify_profile(access=ACCESS_ANALYTICS) def account_summary(account_id): """Return summary for an account. Route: GET /account//summary Path params: account_id (str): Account ID. Query params: start_date (str): Start date YYYY-MM-DD. end_date (str): End date YYYY-MM-DD. type (str): Summary type. Values: TOTAL, STORE, TRACK, SOUND_RECORDING_FAMILY, COUNTRY, IMPRINT, PARTICIPANT, PRODUCT, PROJECT, SUBACCOUNT, SOS, SUBSCRIPTION. resolution (str): Time resolution. account_type (str): Account type (vendor/subaccount). countries (list[str]): Country codes to filter by. subscription_types (list[str]): Subscription types. distributors (list[str]): Distributor names. fin_label_ids (list[str]): Financial label IDs. upper_profit_centers (list[str]): Upper profit center IDs. store_ids (list[int]): Store IDs to filter by. ids (list[str]): IDs to filter aggregation by. limit (int): Max results. offset (int): Pagination offset. order_by (str): Sort field. order_dir (str): Sort direction. Snowflake tables: - V_STREAMS_BY_ACCOUNT_TRACK_FEED_DISTRIBUTOR_DAILY - V_STREAMS_BY_ACCOUNT_TRACK_COUNTRY_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_COUNTRY_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_TRACK_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_TRACK_COUNTRY_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_TRACK_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_TRACK_COUNTRY_FEED_DISTRIBUTOR_DAILY - METRICS_BY_ACCOUNT_28_DAYS_ROLLUP JOINs: - DIM_RELEASE - MAPPINGS_PRODUCT_TO_PRIMARY_GLOBAL_PARTICIPANT - PROJECT - MAPPINGS_PRODUCT_FAMILY_TO_ISRCS - MAPPINGS_FINANCIAL_LABEL_ID_TO_PRODUCT_IDS - MAPPINGS_UPPER_PROFIT_CENTER_TO_PRODUCT_IDS - DIM_SUBACCOUNT Returns: JSON with summary items. """ path_params = {"account_id": account_id} query_params = _get_query_string_params( [ "start_date", "end_date", "type", "resolution", "limit", "offset", "order_by", "order_dir", ], path_params, ) query_params = { **query_params, "account_type": request.args.get("account_type"), "countries": request.args.getlist("countries"), "subscription_types": request.args.getlist("subscription_types"), "distributors": request.args.getlist("distributors"), "fin_label_ids": request.args.getlist("fin_label_ids"), "upper_profit_center_ids": request.args.getlist("upper_profit_centers"), "store_ids": request.args.getlist("store_ids"), "summary": True, "ids": request.args.getlist("ids"), "transfer_product_ownership_enabled": is_insights_transfer_product_ownership_enabled(), "line_soundcloud_collection_as_active_enabled": is_insights_line_soundcloud_collection_as_active_enabled(), } permissions = get_permission_values() # do not return data for a vendor if a user has permissions only to subaccount if ( query_params["account_type"] == "vendor" and permissions["permission_subaccount_ids"] and not permissions["permission_label_ids"] and not user_has_full_access(permissions) ): return jsonify({"items": []}), 200 summary = get_account_summary(query_params, permissions) response_body = {"items": summary} return jsonify(response_body), 200 @app.route(config.ACCOUNT_PRODUCTS_PATH) @verify_profile(access=ACCESS_ANALYTICS) def account_products(account_id): """Return products for an account. Route: GET /account//products Path params: account_id (str): Account ID. Query params: account_type (str): Account type (vendor/subaccount). start_date (str): Start date YYYY-MM-DD. end_date (str): End date YYYY-MM-DD. store_ids (list[int]): Store IDs to filter by. countries (list[str]): Country codes to filter by. sale_start_date_months_back (int): Months back filter (default 0). deletions (str): Include deletions; any non-empty value is truthy (default false). order_by (str): Sort field (required). order_dir (str): Sort direction (default "DESC"). limit (int): Max results (default 5). Snowflake tables: Streams (fixed period): - METRICS_BY_PRODUCT_FEED_DISTRIBUTOR_ROLLUP - METRICS_BY_PRODUCT_COUNTRY_FEED_DISTRIBUTOR_ROLLUP Streams (custom period): - V_STREAMS_BY_PRODUCT_TRACK_FEED_DISTRIBUTOR_DAILY - V_STREAMS_BY_PRODUCT_TRACK_COUNTRY_FEED_DISTRIBUTOR_DAILY Downloads (fixed period): - DOWNLOADS_BY_PRODUCT_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_COUNTRY_FEED_DISTRIBUTOR_DAILY Downloads (custom period): - DOWNLOADS_BY_PRODUCT_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_PRODUCT_COUNTRY_FEED_DISTRIBUTOR_DAILY JOINs: - DIM_RELEASE - DIM_FEED (store_ids subquery) - DATA_AVAILABILITY_BY_STORE_DISTRIBUTOR_DAILY - DATA_AVAILABILITY_GET_MAX_AVAILABLE_STREAMING_STORES_DATE (_PUBLISHED twin when insights_published_max_available_date is on) Returns: JSON with account product items. """ query_params = { "account_type": request.args.get("account_type"), "account_id": account_id, "start_date": request.args.get("start_date"), "end_date": request.args.get("end_date"), "store_ids": request.args.getlist("store_ids"), "countries": request.args.getlist("countries"), "sale_start_date_months_back": int( request.args.get("sale_start_date_months_back", 0) ), "deletions": bool(request.args.get("deletions", False)), "order_by": request.args.get( "order_by" ), # no need to set default value, the code downstream # will raise a Validation error if it is not passed "order_dir": request.args.get("order_dir", "DESC"), "limit": int(request.args.get("limit", 5)), "transfer_product_ownership_enabled": is_insights_transfer_product_ownership_enabled(), "published_max_available_date_enabled": is_insights_published_max_available_date_enabled(), } permissions = get_permission_values() # do not return data for a vendor if a user has permissions only to subaccount if ( query_params["account_type"] == "vendor" and permissions["permission_subaccount_ids"] and not permissions["permission_label_ids"] and not user_has_full_access(permissions) ): return jsonify({"items": []}), 200 releases = get_products(query_params, permissions) response_body = {"items": releases} return jsonify(response_body), 200 @app.route(config.ACCOUNT_TOP_CONTENT_PATH) @verify_profile(access=ACCESS_ANALYTICS) def account_top_content(account_id): """Return top content for an account. Route: GET /account//top-content Path params: account_id (str): Account ID. Query params: start_date (str): Start date YYYY-MM-DD. end_date (str): End date YYYY-MM-DD. account_type (str): Account type (vendor/subaccount). aggregation_type (str): "streams" or "downloads" (default "streams"). store_ids (list[int]): Store IDs to filter by. countries (list[str]): Country codes to filter by. distributors (list[str]): Distributor names. top_size (int): Number of top items (default 5). Snowflake tables: Streams (custom period): - V_STREAMS_BY_ACCOUNT_TRACK_COUNTRY_FEED_DISTRIBUTOR_DAILY Streams (all-time): - METRICS_BY_ACCOUNT_TRACK_COUNTRY_FEED_DISTRIBUTOR_ROLLUP Downloads: - DOWNLOADS_BY_PRODUCT_COUNTRY_FEED_DISTRIBUTOR_DAILY - DOWNLOADS_BY_TRACK_COUNTRY_FEED_DISTRIBUTOR_DAILY JOINs: - DIM_RELEASE - MAPPINGS_PRODUCT_TO_PRIMARY_GLOBAL_PARTICIPANT Returns: JSON with top artists, products, songs, countries, stores. """ path_params = {"account_id": account_id} query_params = _get_query_string_params(["start_date", "end_date"], path_params) query_params = { **query_params, "account_type": request.args.get("account_type"), "aggregation_type": request.args.get("aggregation_type", "streams"), "store_ids": request.args.getlist("store_ids"), "countries": request.args.getlist("countries"), "distributors": request.args.getlist("distributors"), "top_size": int(request.args.get("top_size", 5)), "transfer_product_ownership_enabled": is_insights_transfer_product_ownership_enabled(), } permissions = get_permission_values() # do not return data for a vendor if a user has permissions only to subaccount if ( query_params["account_type"] == "vendor" and permissions["permission_subaccount_ids"] and not permissions["permission_label_ids"] and not user_has_full_access(permissions) ): return ( jsonify( { "topn_artists": [], "topn_products": [], "topn_songs": [], "topn_countries": [], "topn_stores": [], } ), 200, ) top_content = get_top_content(query_params, permissions) response_body = {**top_content} return jsonify(response_body), 200 @app.route(config.ACCOUNT_VIDEO_METRICS_SUMMARY_PATH) @verify_profile(access=ACCESS_ANALYTICS) def account_video_summary(account_id): """Return video summary for an account. Route: GET /account//video/summary Path params: account_id (str): Account ID. Query params: start_date (str): Start date YYYY-MM-DD. end_date (str): End date YYYY-MM-DD. type (str): Summary type (e.g., TOTAL, COUNTRY, PARTICIPANT, TRACK, TRACK_FAMILY). countries (list[str]): Country codes to filter by. distributors (list[str]): Distributor names. views_type (str): Views type filter. limit (int): Max results (default 25). Snowflake tables: - V_VIEWS_BY_ACCOUNT_VIDEO_DISTRIBUTOR_DAILY - V_VIEWS_BY_ACCOUNT_VIDEO_COUNTRY_DISTRIBUTOR_DAILY JOINs: - YOUTUBE_VIDEO - MAPPINGS_VIDEO_TO_PARTICIPANTS_BY_ACCOUNT - MAPPINGS_VIDEO_TO_TRACKS_V3 - MAPPINGS_PRODUCT_FAMILY_TO_ISRCS - STREAMS_BY_TRACK_FEED_DISTRIBUTOR_ROLLUP Returns: JSON with video summary items. """ path_params = {"account_id": account_id} query_params = _get_query_string_params( ["start_date", "end_date", "type"], path_params, ) query_params = { **query_params, # TODO: feature not yet available on subaccount page, so for now it will default to vendor regardless of the passed value # "account_type": request.args.get("account_type"), "account_type": "vendor", "countries": request.args.getlist("countries"), "distributors": request.args.getlist("distributors"), "views_type": request.args.get("views_type"), "type": request.args.get("type"), "limit": request.args.get("limit", 25), "transfer_product_ownership_enabled": is_insights_transfer_product_ownership_enabled(), } permissions = get_permission_values() # do not return data for a vendor if a user has permissions only to subaccount if ( query_params["account_type"] == "vendor" and permissions["permission_subaccount_ids"] and not permissions["permission_label_ids"] and not user_has_full_access(permissions) ): return jsonify({"items": []}), 200 summary = get_account_video_summary(query_params, permissions) response_body = {"items": summary} return jsonify(response_body), 200 @app.route(config.ACCOUNT_VIDEO_METRICS_TIME_SERIES_PATH) @verify_profile(access=ACCESS_ANALYTICS) def account_video_timeseries(account_id): """Return video timeseries for an account. Route: GET /account//video/timeseries Path params: account_id (str): Account ID. Query params: start_date (str): Start date YYYY-MM-DD. end_date (str): End date YYYY-MM-DD. type (str): Timeseries type (e.g., TOTAL, COUNTRY, PARTICIPANT, TRACK, TRACK_FAMILY). views_type (str): Views type filter. distributors (list[str]): Distributor names. countries (list[str]): Country codes to filter by. limit (int): Max results (default 25). Snowflake tables: - V_VIEWS_BY_ACCOUNT_VIDEO_DISTRIBUTOR_DAILY - V_VIEWS_BY_ACCOUNT_VIDEO_COUNTRY_DISTRIBUTOR_DAILY JOINs: - YOUTUBE_VIDEO - MAPPINGS_VIDEO_TO_PARTICIPANTS_BY_ACCOUNT - MAPPINGS_VIDEO_TO_TRACKS_V3 - MAPPINGS_PRODUCT_FAMILY_TO_ISRCS - STREAMS_BY_TRACK_FEED_DISTRIBUTOR_ROLLUP Returns: JSON with video timeseries items. """ path_params = {"account_id": account_id} query_params = _get_query_string_params( ["start_date", "end_date", "type", "views_type", "distributors"], path_params ) query_params = { **query_params, # TODO: feature not yet available on subaccount page, so for now it will default to vendor regardless of the passed value # "account_type": request.args.get("account_type"), "account_type": "vendor", "countries": request.args.getlist("countries"), "distributors": request.args.getlist("distributors"), "is_timeseries": True, "views_type": request.args.get("views_type"), "type": request.args.get("type"), "limit": request.args.get("limit", 25), "transfer_product_ownership_enabled": is_insights_transfer_product_ownership_enabled(), } permissions = get_permission_values() # do not return data for a vendor if a user has permissions only to subaccount if ( query_params["account_type"] == "vendor" and permissions["permission_subaccount_ids"] and not permissions["permission_label_ids"] and not user_has_full_access(permissions) ): return jsonify({"items": []}), 200 timeseries = get_account_video_timeseries(query_params, permissions) response_body = {"items": timeseries} return jsonify(response_body), 200