feat: scale HIFFI pipeline to 500 artists with dual BVI ranking, dark dashboard, and Excel audit export
This commit is contained in:
1 parent
5d441ffe7c
commit
9710cf9948
14 files changed
+3017
-358
No files matched your search
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
core/health_detector.py
|
||||
Identifies inactive, legacy, zombie, and hyper-viral channels
|
||||
based on engagement-to-audience ratios and upload cadence.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class ChannelHealthDetector:
|
||||
"""
|
||||
Evaluates channel vitality by examining daily velocity against subscriber scale.
|
||||
"""
|
||||
|
||||
def __init__(self, zombie_sub_threshold: int = 25000, zombie_velocity_cutoff: float = 100.0):
|
||||
self.zombie_sub_threshold = zombie_sub_threshold
|
||||
self.zombie_velocity_cutoff = zombie_velocity_cutoff
|
||||
|
||||
def classify_channel(self, row: pd.Series) -> Dict[str, Any]:
|
||||
subs = row.get("subscriber_count")
|
||||
daily_vel = row.get("daily_view_velocity")
|
||||
status_flag = row.get("status_flag", "")
|
||||
|
||||
# Handle unresolvable or missing data
|
||||
if pd.isna(subs) or status_flag == "FAILED_LOOKUP":
|
||||
return {
|
||||
"health_status": "UNRESOLVED",
|
||||
"is_zombie": False,
|
||||
"health_notes": "Channel could not be resolved via YouTube API."
|
||||
}
|
||||
|
||||
subs = float(subs)
|
||||
daily_vel = float(daily_vel) if pd.notna(daily_vel) else 0.0
|
||||
|
||||
engagement_ratio = daily_vel / max(subs, 1000.0)
|
||||
|
||||
# 1. Zombie Channel: Large subscriber base with dead viewership
|
||||
if subs >= self.zombie_sub_threshold and daily_vel < self.zombie_velocity_cutoff:
|
||||
return {
|
||||
"health_status": "ZOMBIE_CHANNEL",
|
||||
"is_zombie": True,
|
||||
"health_notes": f"High subscriber base ({int(subs):,}) with negligible viewership ({daily_vel:.1f} views/day)."
|
||||
}
|
||||
|
||||
# 2. Dormant Catalog: No recent uploads and negligible velocity
|
||||
if status_flag == "INACTIVE_OR_DORMANT" or daily_vel == 0.0:
|
||||
return {
|
||||
"health_status": "DORMANT_CATALOG",
|
||||
"is_zombie": subs >= 10000,
|
||||
"health_notes": "Zero recent uploads and stalled catalog velocity."
|
||||
}
|
||||
|
||||
# 3. Viral Surge: Recent views exceed subscriber base
|
||||
if engagement_ratio >= 1.0:
|
||||
return {
|
||||
"health_status": "VIRAL_SURGE",
|
||||
"is_zombie": False,
|
||||
"health_notes": "Audience reach is exploding beyond existing subscriber base."
|
||||
}
|
||||
|
||||
# 4. Catalog Evergreen: Big catalog keeping views high despite no new release
|
||||
if status_flag == "CATALOG_ACTIVE_NO_RECENT_UPLOAD":
|
||||
return {
|
||||
"health_status": "CATALOG_EVERGREEN",
|
||||
"is_zombie": False,
|
||||
"health_notes": "Strong passive catalog views despite release pause."
|
||||
}
|
||||
|
||||
# 5. Healthy Active Channel
|
||||
return {
|
||||
"health_status": "ACTIVE_HEALTHY",
|
||||
"is_zombie": False,
|
||||
"health_notes": "Consistent release schedule and steady audience engagement."
|
||||
}
|
||||
|
||||
def evaluate_dataframe(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Applies health classification across all artist records."""
|
||||
out = df.copy()
|
||||
health_results = out.apply(self.classify_channel, axis=1)
|
||||
|
||||
out["health_status"] = [r["health_status"] for r in health_results]
|
||||
out["is_zombie"] = [r["is_zombie"] for r in health_results]
|
||||
out["health_notes"] = [r["health_notes"] for r in health_results]
|
||||
|
||||
return out
|
||||
Reference in new issue
Block a user