diff --git a/test_multiplatform_ranking.py b/test_multiplatform_ranking.py new file mode 100644 index 0000000..eee8501 --- /dev/null +++ b/test_multiplatform_ranking.py @@ -0,0 +1,174 @@ +""" +test_multiplatform_ranking.py +Simulates the expanded multi-platform HIFFI formula on the pilot cohort. +Merges YouTube metrics from enriched_artists.csv with Spotify and Instagram signals. +""" + +import pandas as pd +import numpy as np +from collectors.browser_collector import MultiPlatformSignalCollector, sync_playwright + +PILOT_ARTISTS = [ + { + "name": "Kendrick Lamar", + "spotify_url": "https://open.spotify.com/artist/2YZyLoL8N0Wb9xBt1NhZWg", + "ig_url": "https://www.instagram.com/kendricklamar/", + }, + { + "name": "Drake", + "spotify_url": "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4", + "ig_url": "https://www.instagram.com/champagnepapi/", + }, + { + "name": "Central Cee", + "spotify_url": "https://open.spotify.com/artist/5H4ikTs84ffUwPVEMOzPt0", + "ig_url": "https://www.instagram.com/centralcee/", + }, + { + "name": "GloRilla", + "spotify_url": "https://open.spotify.com/artist/2qoQgPAilUQnvEBEnfZQI8", + "ig_url": "https://www.instagram.com/glorillapimp/", + }, + { + "name": "Sexyy Red", + "spotify_url": "https://open.spotify.com/artist/1pPmIToKXyG75V4XZgqpvf", + "ig_url": "https://www.instagram.com/sexyyred/", + }, +] + + +def min_max_normalize(series: pd.Series) -> pd.Series: + """Scales numeric values to a 0-100 range.""" + min_v = series.min() + max_v = series.max() + if max_v == min_v: + return pd.Series(50.0, index=series.index) + return ((series - min_v) / (max_v - min_v)) * 100.0 + + +def main(): + print("=" * 80) + print("STEP 1: COLLECTING LIVE SPOTIFY & INSTAGRAM SIGNALS (PILOT)") + print("=" * 80) + + collector = MultiPlatformSignalCollector(headless=True) + collected_rows = [] + + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + context = browser.new_context( + viewport={"width": 1920, "height": 1080}, + user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + locale="en-US", + ) + page = context.new_page() + + for a in PILOT_ARTISTS: + data = collector.collect_artist(a, page) + collected_rows.append(data) + sp_display = ( + f"{data['spotify_monthly_listeners']:,}" + if data["spotify_monthly_listeners"] is not None + else "None" + ) + ig_display = ( + f"{data['instagram_followers']:,}" + if data["instagram_followers"] is not None + else "None" + ) + print(f"Collected: {data['name']} -> Spotify: {sp_display}, IG: {ig_display}") + + browser.close() + + new_signals_df = pd.DataFrame(collected_rows) + + print("\n" + "=" * 80) + print("STEP 2: MERGING WITH EXISTING YOUTUBE METRICS FROM enriched_artists.csv") + print("=" * 80) + + df_existing = pd.read_csv("enriched_artists.csv") + + df_existing["name_clean"] = df_existing["name"].str.strip().str.lower() + new_signals_df["name_clean"] = new_signals_df["name"].str.strip().str.lower() + + merged_df = pd.merge( + new_signals_df, + df_existing, + on="name_clean", + how="inner", + suffixes=("", "_yt"), + ) + + if merged_df.empty: + matched_indices = [] + for name in new_signals_df["name"]: + match = df_existing[df_existing["name"].str.contains(name, case=False, na=False)] + if not match.empty: + matched_indices.append(match.index[0]) + merged_df = df_existing.loc[matched_indices].copy() + merged_df = merged_df.merge(new_signals_df, left_on="name", right_on="name") + + eval_cols = [ + "name", + "subscriber_count", + "recent_avg_views", + "spotify_monthly_listeners", + "instagram_followers", + "hiffi_score", + ] + pilot_view = merged_df[[c for c in eval_cols if c in merged_df.columns]].copy() + + # Fill uncollected values with 0 + pilot_view["spotify_monthly_listeners"] = pilot_view["spotify_monthly_listeners"].fillna(0) + pilot_view["instagram_followers"] = pilot_view["instagram_followers"].fillna(0) + + print("\n" + "=" * 80) + print("STEP 3: COMPUTING NORMALIZED COMPOSITE MULTI-PLATFORM RANKING") + print("=" * 80) + + # 1. Normalize individual signal channels (0 - 100) + pilot_view["norm_yt_views"] = min_max_normalize(pilot_view["recent_avg_views"]) + pilot_view["norm_spotify"] = min_max_normalize(pilot_view["spotify_monthly_listeners"]) + pilot_view["norm_instagram"] = min_max_normalize(pilot_view["instagram_followers"]) + + # 2. Formula weighting: + # 40% Spotify Streaming (Core Audio Demand) + # 35% YouTube Engagement (Video / Catalog Depth) + # 25% Instagram Reach (Social Following / Cultural Reach) + pilot_view["multi_platform_score"] = ( + (0.40 * pilot_view["norm_spotify"]) + + (0.35 * pilot_view["norm_yt_views"]) + + (0.25 * pilot_view["norm_instagram"]) + ).round(2) + + # 3. Compute Ranks + pilot_view["new_multi_rank"] = ( + pilot_view["multi_platform_score"] + .rank(ascending=False, method="min") + .astype(int) + ) + if "hiffi_score" in pilot_view.columns: + pilot_view["original_yt_rank"] = ( + pilot_view["hiffi_score"].rank(ascending=False, method="min").astype(int) + ) + + display_cols = [ + "name", + "original_yt_rank", + "new_multi_rank", + "multi_platform_score", + "spotify_monthly_listeners", + "instagram_followers", + ] + + print("\nComparison Table (Original YouTube Rank vs. Multi-Platform Rank):") + print( + pilot_view[[c for c in display_cols if c in pilot_view.columns]] + .sort_values("new_multi_rank") + .to_string(index=False) + ) + print("=" * 80) + + +if __name__ == "__main__": + main() \ No newline at end of file