"""Aggregate missing playlists history data.""" from datetime import date, timedelta from typing import Dict, List import athena import config import constants.common as consts import main_db import s3 import utils from logger import logger @utils.timing def add_missing_data(table_type: str, playlist_ids: List[str], hot_hits_date: date) -> int: """Add missing hot hits playlists history data. Args: table_type: Update table type. playlist_ids: Missing playlist ID list. hot_hits_date: Latest hot hits date. Returns: int: Rows count. """ if not playlist_ids: return 0 try: if config.ATHENA_TRY_CREATE_DATABASE: athena.create_database() athena.create_table(table_type) upload_path = s3.get_full_path(table_type, consts.Folder.UPLOAD) download_path = s3.get_full_path(table_type, consts.Folder.DOWNLOAD) logger.debug(f"{table_type}: uploading") main_db.upload_to_s3(table_type, playlist_ids, hot_hits_date, upload_path) logger.debug(f"{table_type}: aggregating") athena.execute_aggregate_query(table_type) input_s3_path = s3.get_relative_path(table_type, consts.Folder.UPLOAD) output_s3_path = s3.get_relative_path(table_type, consts.Folder.DOWNLOAD) s3.delete_s3_files_except_csv(output_s3_path) s3.delete_folder_files(input_s3_path) logger.debug(f"{table_type}: downloading") result = main_db.download_from_s3(table_type, download_path) s3.delete_folder_files(output_s3_path) finally: athena.drop_table() s3.delete_temp() return result @utils.timing def set_previous_positions(playlist_ids: List[str], hot_hits_date: date): """Set previous positions. Args: playlist_ids: Playlist ID list. hot_hits_date: Latest date. """ previous_date_from = hot_hits_date - timedelta(days=config.PREVIOUS_POSITION_INTERVAL) previous_date_to = hot_hits_date - timedelta(days=1) latest_date_from = previous_date_from + timedelta(days=1) logger.info( f"stats: HH latest date {hot_hits_date}, prev from {previous_date_from}, latest from {latest_date_from}" ) for index, playlist_id in enumerate(playlist_ids): response = main_db.stats.get_previous_positions( playlist_id, previous_date_from, previous_date_to, latest_date_from, hot_hits_date ) # ISRC to previous different position and its date mapping previous_positions = {} # ISRC to latest position and max available previous date mapping # using this we can set previous equal to latest position to stats items that not in previous_positions latest_positions = {} for record in response: isrc = record.isrc.upper() if isrc not in latest_positions or record.date > latest_positions[isrc][0]: latest_positions[isrc] = (record.date, record.latest_position) if ( record.playlist_index != record.latest_position and (isrc not in previous_positions or previous_positions[isrc][0] < record.date) ): previous_positions[isrc] = (record.date, record.playlist_index) result = [] for isrc, data in latest_positions.items(): previous_date, previous_position = previous_positions[isrc] if isrc in previous_positions else data result.append({"isrc": isrc, "previous_position": previous_position, "previous_date": previous_date}) if result: main_db.stats.update(playlist_id, result) logger.info(f"stats: {index + 1}/{len(playlist_ids)} {playlist_id} {len(latest_positions)} records updated") def drop_aggregate_one_table(table_type: str, playlist_ids: List[str], hot_hits_date: date) -> bool: """Drop extra and aggregate missing for stats table. Args: table_type: Update table type. playlist_ids: All hot hits playlists IDs. hot_hits_date: Actual hot hits date. Returns: Full new or not. """ # latest date affects only stats table where we need to have stats for this date db_obj = main_db.db[table_type] aggregated_ids = db_obj.get_playlists() missing_ids = list(set(playlist_ids) - set(aggregated_ids)) if missing_ids: missing_ids = main_db.check_playlists_in_history(missing_ids) excess_ids = list(set(aggregated_ids) - set(playlist_ids)) if excess_ids: logger.info(f"{table_type}: {excess_ids} obsolete playlists") row_count = db_obj.delete_playlists(excess_ids) logger.info(f"{table_type}: {row_count} obsolete records removed") if missing_ids: logger.info(f"{table_type}: {missing_ids} missing playlists") row_count = add_missing_data(table_type, missing_ids, hot_hits_date) logger.info(f"{table_type}: {row_count} new records added") if config.FORCE_UPDATE_PREVIOUS_POSITIONS: missing_ids = playlist_ids if table_type == consts.Table.STATS and missing_ids: logger.info(f"{table_type}: setting previous positions") set_previous_positions(missing_ids, hot_hits_date) return not aggregated_ids def drop_excess_and_aggregate(playlist_ids: List[str], hot_hits_date: date) -> Dict[str, bool]: """Drop excess and add missing. Args: playlist_ids: HH playlists. hot_hits_date: Actual hot hits date. Returns: Full new or not per table type. """ result = {} for table_type in config.UPDATE_TABLE_LIST: result[table_type] = drop_aggregate_one_table(table_type, playlist_ids, hot_hits_date) return result