feat(governance): implement publication eligibility gates, proportional dynamic redistribution, CI workflow, and 20/20 QA suite
HIFFI QA & Governance Pipeline / governance-tests (push) Successful in 1m43s

This commit is contained in:
Srilekha committed 2026-10-26 12:39:08 +05:30
1 parent 05da5050df
commit 0b36fff7b1
12 files changed
+1325 -806

No files matched your search

+53 -18
View File
@@ -2,13 +2,14 @@
collectors/instagram_verifier.py
Automated candidate matching and verification engine for Instagram profiles.
Replaces brittle static handle overrides with multi-signal evidence verification.
Includes Levenshtein token similarity, bio music heuristics, and review queue integration.
"""
from datetime import datetime, timezone
import json
from pathlib import Path
import re
import sys
from pathlib import Path
from typing import Dict, List, Optional, Tuple
PROJECT_ROOT = Path(__file__).resolve().parent.parent
@@ -16,6 +17,7 @@ if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
OVERRIDES_PATH = PROJECT_ROOT / "data" / "handle_overrides.json"
REVIEW_QUEUE_PATH = PROJECT_ROOT / "data" / "review_queue.csv"
def pd_isna(val) -> bool:
@@ -26,11 +28,34 @@ def pd_isna(val) -> bool:
return False
def levenshtein_ratio(s1: str, s2: str) -> float:
"""Computes Levenshtein similarity ratio [0.0, 1.0] in pure Python."""
if not s1 and not s2:
return 1.0
if not s1 or not s2:
return 0.0
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][0] = i
for j in range(n + 1):
dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = 1 + min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
distance = dp[m][n]
return 1.0 - (distance / max(m, n))
class InstagramCandidateVerifier:
"""
Evaluates Instagram account candidates using name matching, bio link analysis,
and follower plausibility heuristics. Emits confidence scores and audit evidence.
"""
MUSIC_BIO_KEYWORDS = [
"booking", "management", "mgmt", "records", "stream",
"album", "single", "out now", "tour", "official", "ent", "music"
]
def __init__(self, overrides_path: Optional[Path] = None):
self.overrides_path = overrides_path or OVERRIDES_PATH
self.manual_overrides: Dict[str, object] = {}
@@ -145,16 +170,21 @@ class InstagramCandidateVerifier:
confidence += 0.30
evidence.append("Substring containment between artist name and handle")
else:
# Token Overlap Check
artist_tokens = self.extract_tokens(clean_artist)
handle_tokens = self.extract_tokens(handle)
overlap = [t for t in artist_tokens if t in clean_handle or any(t in ht for ht in handle_tokens)]
if overlap:
confidence += 0.25
evidence.append(f"Token overlap identified: {overlap}")
# Fuzzy Levenshtein & Token Overlap Check
sim_score = levenshtein_ratio(clean_artist_norm, clean_handle)
if sim_score >= 0.82:
confidence += 0.30
evidence.append(f"High Levenshtein similarity ({sim_score:.2f})")
else:
confidence += 0.10
evidence.append("Candidate handle linked from cohort")
artist_tokens = self.extract_tokens(clean_artist)
handle_tokens = self.extract_tokens(handle)
overlap = [t for t in artist_tokens if t in clean_handle or any(t in ht for ht in handle_tokens)]
if overlap:
confidence += 0.25
evidence.append(f"Token overlap identified: {overlap}")
else:
confidence += 0.10
evidence.append("Candidate handle linked from cohort")
# Profile Display Name Alignment
clean_profile = self.clean_text(profile_name)
@@ -170,13 +200,18 @@ class InstagramCandidateVerifier:
confidence += 0.15
evidence.append(f"Handle correlates with YouTube channel '{youtube_channel_name}'")
# Cross-Linked External Proof (Linktree / Spotify / etc.)
# Cross-Linked External Proof (Linktree / Spotify / Apple)
links = bio_links or []
bio_lower = bio_raw.lower()
if any("linktr.ee" in l.lower() or "beacons.ai" in l.lower() or "spotify.com" in l.lower() for l in links) or \
any(domain in bio_lower for domain in ["linktr.ee", "spotify.com", "music.apple.com", "official"]):
any(domain in bio_lower for domain in ["linktr.ee", "spotify.com", "music.apple.com"]):
confidence += 0.25
evidence.append("Bio contains cross-platform music or Linktree link")
evidence.append("Bio contains cross-platform music link")
# Music Bio Metadata Heuristics
if any(kw in bio_lower for kw in self.MUSIC_BIO_KEYWORDS):
confidence += 0.15
evidence.append("Bio contains industry music metadata")
# Follower Count Credibility Gate
has_valid_followers = False
@@ -205,7 +240,7 @@ class InstagramCandidateVerifier:
else:
status = "REJECTED_LOW_CONFIDENCE"
# Preserve follower signals for both verified and review accounts; only strip on rejection
# Preserve follower signals for verified and review accounts; drop on low confidence rejection
retained_followers = follower_count if status in ("MANUAL_VERIFIED", "AUTOMATED_VERIFIED", "UNCERTAIN_NEEDS_REVIEW") else None
return {
+50 -10
View File
@@ -3,11 +3,13 @@ collectors/kworb_spotify_collector.py
Audited public streaming telemetry collector.
Extracts verified Spotify Monthly Listeners and Daily Change (+/-) from Kworb.net tables.
Enforces strict null governance (unlisted artists remain None/NaN).
Includes stylized hip-hop stage name normalization and alias resolution.
"""
import requests
from bs4 import BeautifulSoup
import re
from typing import Dict, Optional, Tuple
from bs4 import BeautifulSoup
import requests
KWORB_LISTENERS_URLS = [
"https://kworb.net/spotify/listeners.html",
@@ -23,11 +25,43 @@ HEADERS = {
class KworbStreamingCollector:
# Curated canonical alias map for stylized hip-hop stage names
STAGE_ALIASES: Dict[str, str] = {
"a$ap rocky": "asap rocky",
"a$ap ferg": "asap ferg",
"a$ap mob": "asap mob",
"joey bada$$": "joey badass",
"6ix9ine": "sixnine",
"curren$y": "currensy",
"ty dolla $ign": "ty dolla sign",
"kool g rap": "kool g rap",
"t-pain": "tpain",
"jay-z": "jay z",
"e-40": "e40",
}
def __init__(self):
# Maps lowercase artist_name -> (monthly_listeners, daily_change)
# Maps normalized artist_name -> (monthly_listeners, daily_change)
self.cached_metrics: Dict[str, Tuple[int, Optional[int]]] = {}
self.is_loaded = False
@classmethod
def normalize_artist_name(cls, name: str) -> str:
"""
Normalizes artist names by resolving currency symbols, stripping
extraneous punctuation, and mapping known stage aliases.
"""
if not name:
return ""
norm = name.strip().lower()
if norm in cls.STAGE_ALIASES:
norm = cls.STAGE_ALIASES[norm]
# Standardize common hip-hop typography substitutions
norm = norm.replace("$", "s").replace("&", "and")
norm = re.sub(r"[^\w\s]", "", norm)
return re.sub(r"\s+", " ", norm).strip()
def load_aggregator_tables(self, max_pages: int = 2):
"""Scrapes and indexes artist listener counts and daily velocity from public tables."""
print(f"[KworbCollector] Indexing public streaming metrics across {max_pages} tables...")
@@ -40,8 +74,9 @@ class KworbStreamingCollector:
for row in rows:
cols = row.find_all("td")
if len(cols) >= 4:
# Col 1: Artist
artist_name = cols[1].get_text(strip=True).lower()
# Col 1: Raw Artist Name
raw_artist = cols[1].get_text(strip=True)
artist_norm = self.normalize_artist_name(raw_artist)
# Col 2: Monthly Listeners
raw_listeners = cols[2].get_text(strip=True).replace(",", "")
@@ -57,7 +92,8 @@ class KworbStreamingCollector:
except ValueError:
daily_change = None
self.cached_metrics[artist_name] = (listeners, daily_change)
if artist_norm:
self.cached_metrics[artist_norm] = (listeners, daily_change)
except Exception as e:
print(f"[KworbCollector] Error reading {url}: {e}")
continue
@@ -66,10 +102,14 @@ class KworbStreamingCollector:
print(f"[KworbCollector] Loaded {len(self.cached_metrics):,} artist telemetry records.")
def get_artist_metrics(self, artist_name: str) -> Tuple[Optional[int], Optional[int]]:
"""Returns (monthly_listeners, daily_change) or (None, None)."""
"""
Returns (monthly_listeners, daily_change) or (None, None).
Searches both the direct normalization and alias substitutions.
"""
if not self.is_loaded:
self.load_aggregator_tables(max_pages=2)
return self.cached_metrics.get(artist_name.strip().lower(), (None, None))
norm_key = self.normalize_artist_name(artist_name)
return self.cached_metrics.get(norm_key, (None, None))
def get_monthly_listeners(self, artist_name: str) -> Optional[int]:
return self.get_artist_metrics(artist_name)[0]
@@ -83,9 +123,9 @@ if __name__ == "__main__":
collector.load_aggregator_tables(max_pages=2)
test_cohort = [
"Drake", "Kendrick Lamar", "SZA", "Future",
"Cardi B", "Russ Millions", "Larry June", "Rylo Rodriguez"
"Cardi B", "A$AP Rocky", "Joey Bada$$", "6ix9ine", "Ty Dolla $ign"
]
print("\nValidating Kworb Stream & Daily Velocity Extraction:")
print("\nValidating Kworb Stream & Daily Velocity Extraction with Aliases:")
print(f"{'Artist':20} | {'Monthly Listeners':>18} | {'Daily +/- Trend':>16}")
print("-" * 60)
for artist in test_cohort: