feat: scale HIFFI pipeline to 500 artists with dual BVI ranking, dark dashboard, and Excel audit export

This commit is contained in:
Srilekha committed 2026-10-22 17:20:44 +05:30
1 parent 5d441ffe7c
commit 9710cf9948
14 files changed
+3017 -358

No files matched your search

+109 -54
View File
@@ -1,60 +1,122 @@
from typing import Dict, Any
"""
core/confidence_scorer.py
Computes composite authenticity confidence scores (0.0 - 1.0)
across YouTube channel titles, MusicBrainz, and Wikidata with
noise-cleaning and cross-referencing.
"""
import re
from typing import Dict, Any, Optional
class ConfidenceScorer:
"""
Computes a weighted authenticity and quality score across multiple data sources:
- YouTube footprint: 40%
- MusicBrainz catalog: 35%
- Wikidata entity: 25%
Evaluates multi-source identity alignment across YouTube, MusicBrainz, and Wikidata.
"""
def __init__(self, yt_weight: float = 0.40, mb_weight: float = 0.35, wd_weight: float = 0.25):
self.w_yt = yt_weight
self.w_mb = mb_weight
self.w_wd = wd_weight
def __init__(
self,
yt_weight: float = 0.40,
mb_weight: float = 0.35,
wd_weight: float = 0.25,
):
self.yt_weight = yt_weight
self.mb_weight = mb_weight
self.wd_weight = wd_weight
@staticmethod
def _clean_string(text: Optional[str]) -> str:
"""Strips common artist channel noise tokens and punctuation."""
if not text:
return ""
s = text.lower()
# Remove suffixes like VEVO, Official, Records, Music, Topic
noise_tokens = [
r"\bvevo\b",
r"\bofficial\b",
r"\btopic\b",
r"\bmusic\b",
r"\brecords\b",
r"\btv\b",
r"\bchannel\b",
]
for token in noise_tokens:
s = re.sub(token, "", s)
# Remove all non-alphanumeric characters
return re.sub(r"[^a-z0-9]", "", s)
def _match_youtube(self, artist_name: str, yt_title: Optional[str]) -> float:
"""Evaluates whether the YouTube channel title matches the artist."""
if not yt_title or not artist_name:
return 0.0
clean_artist = self._clean_string(artist_name)
clean_title = self._clean_string(yt_title)
if not clean_artist or not clean_title:
return 0.0
# Exact match after cleaning (e.g. 'KendrickLamarVEVO' -> 'kendricklamar' == 'kendricklamar')
if clean_artist == clean_title:
return 1.0
# Substring containment
if clean_artist in clean_title or clean_title in clean_artist:
return 0.85
return 0.0
def calculate_score(
self,
yt_metrics: Dict[str, Any],
mb_result: Dict[str, Any],
wd_result: Dict[str, Any]
wd_result: Dict[str, Any],
) -> Dict[str, Any]:
# 1. YouTube sub-score (0.0 to 1.0)
yt_score = 0.0
if yt_metrics.get("status") in ["live_api", "cached"]:
subs = yt_metrics.get("subscribers", 0)
views = yt_metrics.get("lifetime_views", 0)
vids = yt_metrics.get("video_count", 0)
"""
Computes composite confidence based on:
- YouTube channel name alignment (40%)
- MusicBrainz match verification (35%)
- Wikidata entity validation (25%)
- Cross-referencing bonus: MBID linked directly inside Wikidata
"""
artist_name = yt_metrics.get("channel_title") or ""
# If artist name was provided separately in the identifier dict, we compare against channel_title
yt_title = yt_metrics.get("channel_title")
raw_name = yt_metrics.get("name") or yt_title
# Baseline 0.5 for a verified channel existence
yt_score = 0.5
if subs >= 1000:
yt_score += 0.2
elif subs > 0:
yt_score += 0.1
# 1. YouTube Title Alignment Score
yt_score = self._match_youtube(raw_name, yt_title)
if views >= 50000:
yt_score += 0.2
elif views > 0:
yt_score += 0.1
# 2. MusicBrainz Score
mb_match = mb_result.get("verified", False) if isinstance(mb_result, dict) else bool(mb_result)
mb_score = 1.0 if mb_match else 0.0
if vids >= 5:
yt_score += 0.1
# 3. Wikidata Score
wd_match = wd_result.get("verified", False) if isinstance(wd_result, dict) else bool(wd_result)
wd_score = 1.0 if wd_match else 0.0
# 2. MusicBrainz sub-score
mb_score = mb_result.get("confidence", 0.0) if mb_result.get("verified") else 0.0
# 4. Cross-Referencing Bonus: Wikidata linked to MusicBrainz ID
cross_ref_bonus = 0.0
if isinstance(wd_result, dict) and isinstance(mb_result, dict):
wd_mbid = wd_result.get("musicbrainz_id")
actual_mbid = mb_result.get("mbid")
if wd_mbid and actual_mbid and wd_mbid == actual_mbid:
cross_ref_bonus = 0.05 # Extra 5% confidence for direct KG linkage
# 3. Wikidata sub-score
wd_score = wd_result.get("confidence", 0.0) if wd_result.get("verified") else 0.0
# Composite calculation
composite = (
self.yt_weight * yt_score
+ self.mb_weight * mb_score
+ self.wd_weight * wd_score
+ cross_ref_bonus
)
composite = round(min(composite, 1.0), 2)
# Aggregated composite score
composite = (yt_score * self.w_yt) + (mb_score * self.w_mb) + (wd_score * self.w_wd)
composite = round(min(1.0, max(0.0, composite)), 2)
# Confidence category
if composite >= 0.75:
tier = "High"
elif composite >= 0.45:
tier = "Medium"
# Tier classification
if composite >= 0.80:
tier = "High Confidence"
elif composite >= 0.50:
tier = "Medium Confidence"
else:
tier = "Low / Emerging"
@@ -62,16 +124,9 @@ class ConfidenceScorer:
"composite_confidence": composite,
"confidence_tier": tier,
"sub_scores": {
"youtube": round(yt_score, 2),
"musicbrainz": round(mb_score, 2),
"wikidata": round(wd_score, 2)
}
}
if __name__ == "__main__":
scorer = ConfidenceScorer()
dummy_yt = {"status": "live_api", "subscribers": 5000, "lifetime_views": 250000, "video_count": 18}
dummy_mb = {"verified": True, "confidence": 1.0}
dummy_wd = {"verified": True, "confidence": 1.0}
print("Test Scorer Output:")
print(scorer.calculate_score(dummy_yt, dummy_mb, dummy_wd))
"youtube": yt_score,
"musicbrainz": mb_score,
"wikidata": wd_score,
"cross_referenced": cross_ref_bonus > 0.0,
},
}