test: add test_multiplatform_ranking test suite
This commit is contained in:
1 parent
4aa24d68c7
commit
56cbd08248
1 file changed
+174
@@ -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()
|
||||
Reference in new issue
Block a user