126 lines
4.7 KiB
Python
126 lines
4.7 KiB
Python
"""
|
|
reconcile_sales_telemetry.py
|
|
Hardened reconciliation engine for HIFFI multi-platform dataset.
|
|
Applies industry-standard streaming conversions (Tier 3: ESTIMATED_SEA)
|
|
to uncertified acts and re-indexes cohort sales rankings across all 500 artists.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
import math
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
DATA_PATH = Path("data/hiffi_multiplatform_500.csv")
|
|
|
|
|
|
def clean_num(val) -> float:
|
|
"""Safely converts any input into a float, returning 0.0 for NaNs or None."""
|
|
if pd.isna(val) or val is None:
|
|
return 0.0
|
|
try:
|
|
f = float(val)
|
|
return 0.0 if math.isnan(f) else f
|
|
except (ValueError, TypeError):
|
|
return 0.0
|
|
|
|
|
|
def reconcile_dataset():
|
|
if not DATA_PATH.exists():
|
|
print(f"File not found: {DATA_PATH}")
|
|
return
|
|
|
|
df = pd.read_csv(DATA_PATH)
|
|
print(f"Loaded master dataset with {len(df)} artists.")
|
|
|
|
# Standardize column types
|
|
numeric_fields = [
|
|
"total_eas_cspc",
|
|
"pure_album_sales",
|
|
"streaming_eas",
|
|
"physical_singles_sales",
|
|
"digital_singles_sales",
|
|
"riaa_certified_album_units",
|
|
"riaa_certified_singles_units",
|
|
"spotify_monthly_listeners",
|
|
"lifetime_views",
|
|
]
|
|
for col in numeric_fields:
|
|
if col in df.columns:
|
|
df[col] = pd.to_numeric(df[col], errors="coerce")
|
|
else:
|
|
df[col] = np.nan
|
|
|
|
for idx, row in df.iterrows():
|
|
status = str(row.get("sales_data_status", ""))
|
|
|
|
# 1. Tier 1: CSPC Verified (Untouched)
|
|
if status == "VERIFIED_CSPC":
|
|
continue
|
|
|
|
# 2. Tier 2: RIAA Verified (Standardize EAS Anchor)
|
|
elif status == "VERIFIED_RIAA":
|
|
album_u = clean_num(row.get("riaa_certified_album_units"))
|
|
singles_u = clean_num(row.get("riaa_certified_singles_units"))
|
|
|
|
current_eas = clean_num(row.get("total_eas_cspc"))
|
|
if current_eas == 0.0:
|
|
if album_u > 0:
|
|
df.at[idx, "total_eas_cspc"] = album_u
|
|
df.at[idx, "pure_album_sales"] = album_u
|
|
else:
|
|
# 10 digital singles = 1 album equivalent
|
|
tea = singles_u / 10.0
|
|
df.at[idx, "total_eas_cspc"] = tea
|
|
df.at[idx, "digital_singles_sales"] = singles_u
|
|
|
|
# 3. Tier 3: Apply Streaming SEA fallback for uncertified acts
|
|
else:
|
|
sp_listeners = clean_num(row.get("spotify_monthly_listeners"))
|
|
yt_views = clean_num(row.get("lifetime_views"))
|
|
|
|
if sp_listeners > 0.0:
|
|
# 1 monthly listener generates ~36 annual on-demand audio streams
|
|
# 1,500 audio streams = 1 Album Unit (SEA)
|
|
estimated_annual_streams = sp_listeners * 36.0
|
|
sea_units = float(round(estimated_annual_streams / 1500.0))
|
|
|
|
df.at[idx, "streaming_eas"] = sea_units
|
|
df.at[idx, "total_eas_cspc"] = sea_units
|
|
df.at[idx, "pure_album_sales"] = 0.0
|
|
df.at[idx, "physical_singles_sales"] = 0.0
|
|
df.at[idx, "digital_singles_sales"] = 0.0
|
|
df.at[idx, "sales_data_status"] = "ESTIMATED_SEA"
|
|
elif yt_views > 0.0:
|
|
# Video conversion: 3,750 ad-supported video streams = 1 Album Unit
|
|
sea_units = float(round(yt_views / 3750.0))
|
|
df.at[idx, "streaming_eas"] = sea_units
|
|
df.at[idx, "total_eas_cspc"] = sea_units
|
|
df.at[idx, "pure_album_sales"] = 0.0
|
|
df.at[idx, "physical_singles_sales"] = 0.0
|
|
df.at[idx, "digital_singles_sales"] = 0.0
|
|
df.at[idx, "sales_data_status"] = "ESTIMATED_SEA"
|
|
else:
|
|
# Tier 4: No certified sales, no streams
|
|
df.at[idx, "sales_data_status"] = "UNRANKED_EMERGING"
|
|
df.at[idx, "total_eas_cspc"] = 0.0
|
|
|
|
# Ensure total_eas_cspc has no NaNs
|
|
df["total_eas_cspc"] = df["total_eas_cspc"].fillna(0.0)
|
|
|
|
# Re-calculate Cohort Sales Rank across all 500 artists
|
|
df["cohort_sales_rank"] = df["total_eas_cspc"].rank(ascending=False, method="min")
|
|
|
|
df.to_csv(DATA_PATH, index=False)
|
|
|
|
print("\n==========================================")
|
|
print("Reconciliation Complete!")
|
|
print(f"VERIFIED_CSPC: {(df['sales_data_status'] == 'VERIFIED_CSPC').sum()}")
|
|
print(f"VERIFIED_RIAA: {(df['sales_data_status'] == 'VERIFIED_RIAA').sum()}")
|
|
print(f"ESTIMATED_SEA: {(df['sales_data_status'] == 'ESTIMATED_SEA').sum()}")
|
|
print(f"UNRANKED_EMERGING: {(df['sales_data_status'] == 'UNRANKED_EMERGING').sum()}")
|
|
print(f"Total Cohort Size: {len(df)}")
|
|
print("==========================================")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
reconcile_dataset() |