from typing import List, Dict, Any from charts import config from charts.connectors import sentry from charts.connectors import snowflake from charts.logic import sql_loader from charts.utils.dataloader import dataloader from .dataloaders.charts.utils import ChartIdType from charts.utils.db.mapper import map_db_result_with_column_names from charts.constants.chart_columns import CHART_RANKINGS_VALUES_ON_DATE_COLUMN_NAMES from typing import TypedDict, Literal, Optional from uuid import UUID from charts.logic.permissions import PermissionType from charts.utils.dict import safe_dict_update from charts.logic.dataloaders.charts.dates.placements_dataloader import _chart_placements_brand_filter from datetime import date class GsrsForChartsDateResponse(TypedDict): globalSoundRecordingIsrcs: list[str] class FilterType(TypedDict, total=False): brand_uuid_in: list[UUID | Literal['👾']] def get_charts_by_platforms_with_latest_dates(platforms: List[str]) -> List[Dict[str, Any]]: """Get latest chart dates by passed platform Args: platforms: Array of platforms like "spotify", "apple" etc. Returns: List of dict objects containing chart information """ # Query params # reason for exclude_charts is described IN-10347 and spotify_viral_weekly charts are outdated, no longer exist # but the data is still present in DB so should be removed from original response. # This logic can be changed or removed when data team will solve the problem with the presence of outdated data params = { "platforms": [platform.lower() for platform in platforms], "exclude_charts": config.CHARTS_TO_EXCLUDE } column_names = ( "definition_key_mapping", "chart_name", "platform", "latest_chart_date", ) sql = sql_loader.load_query("get_charts_by_platforms") raw_db_result = snowflake.fetchall(sql, params) return map_db_result_with_column_names(raw_db_result, column_names) def get_charts_by_definition(platform: str, target: str, frequency: str, type: str = None, country_code: str = None, genre: str = None) -> List[Dict[str, Any]]: """Get charts by platform, target, frequency type, country_code, and genre Args: platform: A store e.g. "youtube". target: A string e.g. "track". frequency: A string e.g. "weekly". type: optional string e.g. "music videos" used when platform is not amazon. country_code: optional two letter iso country_code e.g. "US". genre: optional string used only when amazon definition key is provided. Returns: List of chart ids related to definition key """ if(type is None and genre is None): raise ValueError('Either type or genre must be defined') column_names = ( "chartId", ) params = { 'platform': platform.lower(), 'target': target.lower(), 'frequency': frequency.lower() } sql = sql_loader.load_query("get_charts_by_definition_key") additional_where_clauses = [] if platform != 'amazon': params['type'] = " ".join(type.split("_")).lower() additional_where_clauses.append('lower(type) = :type') if genre and platform == 'amazon': params['genre'] = " ".join(genre.split("_")).lower() additional_where_clauses.append('lower(genre) = :genre') if country_code: params['country_code'] = country_code.lower() additional_where_clauses.append('lower(country) = :country_code') additional_where_clause = ' AND '.join(additional_where_clauses) if additional_where_clause: additional_where_clause = f'AND {additional_where_clause}' sql = sql.format( additional_where_clause=additional_where_clause, ) raw_db_result = snowflake.fetchall(sql, params) return map_db_result_with_column_names(raw_db_result, column_names) def _null_if_none_or_quoted(val): return 'null' if val is None else f"'{val}'" def _escape(val): return val.replace("\\", "\\\\").replace("'", "\\'") @dataloader def get_chart_rankings_aggregated_values_on_date_dataloaded(chart_rankings_aggregated_on_date: List[Dict[str, Any]]) -> List[Dict[str, Any]]: sql = sql_loader.load_query('chart_rankings_aggregated_values_on_date_dataloaded') params = { "chart_rankings_aggregated_on_date": [ [f"({i}, '{chart_rank['chart_id']}', '{chart_rank['chart_date']}', {_null_if_none_or_quoted(chart_rank['public_sound_recording_id'])}, {_null_if_none_or_quoted(_escape(chart_rank['chartmetric_track_id']) if chart_rank['chartmetric_track_id'] else None)}, {_null_if_none_or_quoted(chart_rank['public_product_id'])})"] for i, chart_rank in enumerate(chart_rankings_aggregated_on_date) ] } raw_db_result = snowflake.fetchall_nocache(sql, params) results = map_db_result_with_column_names(raw_db_result, CHART_RANKINGS_VALUES_ON_DATE_COLUMN_NAMES) return [ { "data": result if result['id'] else None } for result in results ] def get_global_sound_recordings_for_charts_date( chart_ids: list[ChartIdType], chart_date: date, filter: FilterType, order_by: Literal['FEATURING'] | Literal['MIN_POSITION'], order_dir: Literal['ASC'] | Literal['DESC'], permissions: Optional[PermissionType], ) -> GsrsForChartsDateResponse: params = { "chart_ids": [f"{chart}" for chart in chart_ids], "chart_date": chart_date, } sql = sql_loader.load_query('global_sound_recordings_for_charts_date') if order_by == 'MIN_POSITION': sql = sql.replace('{{ order_by }}', 'min(position)') elif order_by == 'FEATURING': sql = sql.replace('{{ order_by }}', 'count(chartid)') else: raise(NotImplementedError(f'Not implemented {order_by}')) sql = sql.replace('{{ order_dir }}', f"{order_dir}") if 'brand_uuid_in' in filter: assert permissions is not None, 'permissions must be set when using brand_uuid_in filter' sub_sql, sub_params = _chart_placements_brand_filter(filter['brand_uuid_in'], permissions=permissions) safe_dict_update(params, sub_params) sql = sql.replace('{{ brand_filter }}', f'and {sub_sql}') else: sql = sql.replace('{{ brand_filter }}', '') raw_db_result = snowflake.fetchall_nocache(sql, params) results = map_db_result_with_column_names(raw_db_result, ['isrc', 'has_gsr']) resultsWithGsrs = [] for result in results: if not result['has_gsr']: sentry.sentry_client.captureMessage( 'ISRC with no GSR', extra={ 'isrc': result['isrc'], 'params': { 'chart_ids': chart_ids, 'chart_date': chart_date, 'filter': filter, 'order_by': order_by, 'order_dir': order_dir, }, }, ) continue resultsWithGsrs.append(result) return { "globalSoundRecordingIsrcs": [result['isrc'] for result in resultsWithGsrs] }