feat(sales): implement Phase 1 & 2 commercial sales telemetry and dashboard analytics
This commit is contained in:
1 parent
6b567df015
commit
8f77295de9
7 files changed
+1371
-575
No files matched your search
+158
-15
@@ -2,7 +2,8 @@
|
||||
test_multiplatform_ranking.py
|
||||
Comprehensive QA & Data Governance Test Suite for HIFFI Multi-Platform Pipeline.
|
||||
Enforces zero-imputation policy, score bounding, identity resolution,
|
||||
fallbacks, strict null retention, and publication eligibility gates.
|
||||
fallbacks, strict null retention, publication eligibility gates,
|
||||
and ChartMasters record sales governance.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
@@ -175,7 +176,6 @@ def test_spotify_fallback_null_safety(monkeypatch):
|
||||
"""Verify Spotify embed fallback safely returns structured null dictionary on unindexed items."""
|
||||
from collectors.spotify_fallback_collector import SpotifyFallbackCollector
|
||||
|
||||
# Mock unindexed response to avoid brittle offline network failures
|
||||
class MockResponse:
|
||||
status_code = 404
|
||||
text = "Not Found"
|
||||
@@ -353,12 +353,10 @@ def test_publication_eligibility_gates(dataset):
|
||||
engine = HIFFIRankingEngine()
|
||||
gated = engine.apply_publication_eligibility_gates(dataset, reference_time=DATASET_ANCHOR_TIME)
|
||||
|
||||
# 1. Raw scores must remain unchanged
|
||||
assert (gated["hiffi_xp_score"] == dataset["hiffi_xp_score"]).all(), (
|
||||
"Publication gates altered the underlying raw composite scores."
|
||||
)
|
||||
|
||||
# 2. Check Top 10 requires all 3 platforms
|
||||
top_10 = gated[gated["hiffi_xp_published_rank"] <= 10]
|
||||
assert len(top_10) == 10
|
||||
assert (top_10["platform_evidence_tier"] == 3).all(), (
|
||||
@@ -366,7 +364,6 @@ def test_publication_eligibility_gates(dataset):
|
||||
f"{top_10[top_10['platform_evidence_tier'] < 3][['name', 'platform_evidence_tier']]}"
|
||||
)
|
||||
|
||||
# 3. Check Top 50 requires at least 2 platforms
|
||||
top_50 = gated[gated["hiffi_xp_published_rank"] <= 50]
|
||||
assert len(top_50) == 50
|
||||
assert (top_50["platform_evidence_tier"] >= 2).all(), (
|
||||
@@ -374,7 +371,6 @@ def test_publication_eligibility_gates(dataset):
|
||||
f"{top_50[top_50['platform_evidence_tier'] < 2][['name', 'platform_evidence_tier']]}"
|
||||
)
|
||||
|
||||
# 4. Check ranks are strictly unique and consecutive 1 to N
|
||||
ranks = gated["hiffi_xp_published_rank"].sort_values().tolist()
|
||||
expected_ranks = list(range(1, len(gated) + 1))
|
||||
assert ranks == expected_ranks, "Published ranks contain gaps or duplicates."
|
||||
@@ -404,7 +400,6 @@ def test_publication_gate_blocks_failed_youtube_lookup():
|
||||
gated = engine.apply_publication_eligibility_gates(test_df, reference_time=DATASET_ANCHOR_TIME)
|
||||
row = gated.iloc[0]
|
||||
|
||||
# Blocked from YouTube -> only Spotify + IG valid -> Tier 2
|
||||
assert row["platform_evidence_tier"] == 2
|
||||
assert "FAILED_YOUTUBE_LOOKUP" in row["evidence_exclusions"]
|
||||
|
||||
@@ -515,12 +510,12 @@ def test_freshness_gate_stale_timestamps_demote_tier():
|
||||
"name": "Artist Stale Telemetry",
|
||||
"status_flag": "COMPLETE",
|
||||
"production_hiffi_score": 90.0,
|
||||
"youtube_updated_at": "2026-10-25T12:00:00Z", # 1 day old (Fresh)
|
||||
"youtube_updated_at": "2026-10-25T12:00:00Z",
|
||||
"spotify_monthly_listeners": 25_000_000,
|
||||
"spotify_updated_at": "2026-10-15T12:00:00Z", # 11 days old (Stale)
|
||||
"spotify_updated_at": "2026-10-15T12:00:00Z",
|
||||
"instagram_followers": 10_000_000,
|
||||
"instagram_verification_status": "AUTOMATED_VERIFIED",
|
||||
"instagram_updated_at": "2026-10-01T12:00:00Z", # 25 days old (Stale)
|
||||
"instagram_updated_at": "2026-10-01T12:00:00Z",
|
||||
"hiffi_xp_score": 88.5,
|
||||
"hiffi_xp_rank": 5
|
||||
}])
|
||||
@@ -548,7 +543,7 @@ def test_freshness_gate_missing_or_malformed_timestamp():
|
||||
"spotify_updated_at": None,
|
||||
"instagram_followers": 5_000_000,
|
||||
"instagram_verification_status": "AUTOMATED_VERIFIED",
|
||||
"instagram_updated_at": "2026-10-25T12:00:00Z", # Fresh
|
||||
"instagram_updated_at": "2026-10-25T12:00:00Z",
|
||||
"hiffi_xp_score": 82.0,
|
||||
"hiffi_xp_rank": 10
|
||||
}])
|
||||
@@ -600,8 +595,8 @@ def test_anomaly_detection_flags_suspicious_patterns():
|
||||
},
|
||||
{
|
||||
"name": "Inorganic IG Artist",
|
||||
"lifetime_views": 100_000, # 100k views
|
||||
"instagram_followers": 5_000_000, # 5M followers (50x ratio > 10x)
|
||||
"lifetime_views": 100_000,
|
||||
"instagram_followers": 5_000_000,
|
||||
"spotify_monthly_listeners": 500_000,
|
||||
"spotify_daily_change": 1_000,
|
||||
"hiffi_xp_score": 70.0
|
||||
@@ -611,7 +606,7 @@ def test_anomaly_detection_flags_suspicious_patterns():
|
||||
"lifetime_views": 50_000_000,
|
||||
"instagram_followers": 1_000_000,
|
||||
"spotify_monthly_listeners": 100_000,
|
||||
"spotify_daily_change": 40_000, # 40k daily change on 100k monthly (40% > 25%)
|
||||
"spotify_daily_change": 40_000,
|
||||
"hiffi_xp_score": 65.0
|
||||
}
|
||||
])
|
||||
@@ -621,4 +616,152 @@ def test_anomaly_detection_flags_suspicious_patterns():
|
||||
assert flagged.loc[0, "anomaly_flags"] == "CLEAN"
|
||||
assert "SUSPICIOUS_IG_TO_YT_RATIO" in flagged.loc[1, "anomaly_flags"]
|
||||
assert "EXTREME_SPOTIFY_VELOCITY_RATIO" in flagged.loc[2, "anomaly_flags"]
|
||||
assert flagged.loc[1, "hiffi_xp_score"] == 70.0
|
||||
assert flagged.loc[1, "hiffi_xp_score"] == 70.0
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# CHARTMASTERS RECORD SALES GOVERNANCE & INTEGRITY TESTS
|
||||
# ==============================================================================
|
||||
|
||||
MOCK_CHARTMASTERS_HTML = """
|
||||
<html>
|
||||
<body>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>Artist</th>
|
||||
<th>Album Sales</th>
|
||||
<th>Physical Singles</th>
|
||||
<th>Digital Singles</th>
|
||||
<th>Streaming EAS</th>
|
||||
<th>Total EAS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>The Beatles</td>
|
||||
<td>162,000,000</td>
|
||||
<td>33,000,000</td>
|
||||
<td>28,000,000</td>
|
||||
<td>10,000,000</td>
|
||||
<td>233,000,000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Michael Jackson</td>
|
||||
<td>135,000,000</td>
|
||||
<td>26,000,000</td>
|
||||
<td>24,000,000</td>
|
||||
<td>12,000,000</td>
|
||||
<td>197,000,000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Eminem</td>
|
||||
<td>63,000,000</td>
|
||||
<td>9,000,000</td>
|
||||
<td>45,000,000</td>
|
||||
<td>60,000,000</td>
|
||||
<td>177,000,000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>A$AP Rocky</td>
|
||||
<td>2,500,000</td>
|
||||
<td>100,000</td>
|
||||
<td>15,000,000</td>
|
||||
<td>20,000,000</td>
|
||||
<td>37,600,000</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def test_chartmasters_parser_schema_and_types():
|
||||
"""Validates HTML table parsing, column typing, and metrics conversion."""
|
||||
from collectors.chartmasters_collector import ChartMastersSalesCollector
|
||||
|
||||
collector = ChartMastersSalesCollector()
|
||||
count = collector.load_sales_table(html_content=MOCK_CHARTMASTERS_HTML)
|
||||
assert count == 4
|
||||
|
||||
beatles = collector.get_artist_sales_metrics("The Beatles")
|
||||
assert beatles["sales_data_status"] == "VERIFIED_CSPC"
|
||||
assert beatles["chartmasters_sales_rank"] == 1
|
||||
assert beatles["total_eas_cspc"] == 233_000_000
|
||||
assert beatles["pure_album_sales"] == 162_000_000
|
||||
assert isinstance(beatles["streaming_eas"], int)
|
||||
|
||||
|
||||
def test_chartmasters_stage_alias_resolution():
|
||||
"""Validates stylized name alias resolution (e.g. A$AP Rocky -> asap rocky)."""
|
||||
from collectors.chartmasters_collector import ChartMastersSalesCollector
|
||||
|
||||
collector = ChartMastersSalesCollector()
|
||||
collector.load_sales_table(html_content=MOCK_CHARTMASTERS_HTML)
|
||||
|
||||
asap = collector.get_artist_sales_metrics("A$AP Rocky")
|
||||
assert asap["sales_data_status"] == "VERIFIED_CSPC"
|
||||
assert asap["chartmasters_sales_rank"] == 4
|
||||
assert asap["total_eas_cspc"] == 37_600_000
|
||||
|
||||
|
||||
def test_chartmasters_zero_imputation_null_retention():
|
||||
"""Ensures uncataloged artists strictly return None and are never imputed with 0."""
|
||||
from collectors.chartmasters_collector import ChartMastersSalesCollector
|
||||
|
||||
collector = ChartMastersSalesCollector()
|
||||
collector.load_sales_table(html_content=MOCK_CHARTMASTERS_HTML)
|
||||
|
||||
unindexed = collector.get_artist_sales_metrics("Emerging Underground Artist 123")
|
||||
assert unindexed["sales_data_status"] == "NOT_CATALOGED_YET"
|
||||
assert unindexed["chartmasters_sales_rank"] is None
|
||||
assert unindexed["total_eas_cspc"] is None
|
||||
assert unindexed["pure_album_sales"] is None
|
||||
|
||||
|
||||
def test_chartmasters_rank_monotonicity():
|
||||
"""Higher sales rank (1 vs 2) must strictly correspond to higher or equal Total EAS."""
|
||||
from collectors.chartmasters_collector import ChartMastersSalesCollector
|
||||
|
||||
collector = ChartMastersSalesCollector()
|
||||
collector.load_sales_table(html_content=MOCK_CHARTMASTERS_HTML)
|
||||
|
||||
beatles = collector.get_artist_sales_metrics("The Beatles")
|
||||
mj = collector.get_artist_sales_metrics("Michael Jackson")
|
||||
eminem = collector.get_artist_sales_metrics("Eminem")
|
||||
|
||||
assert beatles["chartmasters_sales_rank"] < mj["chartmasters_sales_rank"] < eminem["chartmasters_sales_rank"]
|
||||
assert beatles["total_eas_cspc"] >= mj["total_eas_cspc"] >= eminem["total_eas_cspc"]
|
||||
|
||||
|
||||
def test_chartmasters_enrich_cohort_dataframe():
|
||||
"""Tests zero-imputation cohort enrichment over a pandas DataFrame."""
|
||||
from collectors.chartmasters_collector import ChartMastersSalesCollector
|
||||
|
||||
collector = ChartMastersSalesCollector()
|
||||
collector.load_sales_table(html_content=MOCK_CHARTMASTERS_HTML)
|
||||
|
||||
cohort_df = pd.DataFrame([
|
||||
{"name": "Eminem", "production_hiffi_score": 92.0},
|
||||
{"name": "Non-Indexed Indie Artist", "production_hiffi_score": 45.0}
|
||||
])
|
||||
|
||||
enriched = collector.enrich_cohort(cohort_df, name_column="name")
|
||||
|
||||
assert "total_eas_cspc" in enriched.columns
|
||||
assert "chartmasters_sales_rank" in enriched.columns
|
||||
|
||||
# Row 0 (Eminem) has valid numeric metrics
|
||||
assert enriched.loc[0, "total_eas_cspc"] == 177_000_000
|
||||
assert enriched.loc[0, "chartmasters_sales_rank"] == 3
|
||||
|
||||
# Row 1 (Non-indexed) must be None/NaN, not 0
|
||||
assert pd.isna(enriched.loc[1, "total_eas_cspc"])
|
||||
assert pd.isna(enriched.loc[1, "chartmasters_sales_rank"])
|
||||
assert enriched.loc[1, "sales_data_status"] == "NOT_CATALOGED_YET"
|
||||
Reference in new issue
Block a user