2026-10-22 17:20:44 +05:30
|
|
|
"""
|
|
|
|
|
core/confidence_scorer.py
|
2026-10-26 12:39:08 +05:30
|
|
|
Calculates signal freshness, staleness decay, and multi-platform confidence.
|
|
|
|
|
Enforces zero-imputation rules across timestamps and verification confidence.
|
2026-10-22 17:20:44 +05:30
|
|
|
"""
|
|
|
|
|
|
2026-10-26 12:39:08 +05:30
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
from typing import Optional
|
|
|
|
|
import numpy as np
|
|
|
|
|
import pandas as pd
|
2026-10-22 17:20:44 +05:30
|
|
|
|
2026-10-22 13:06:10 +05:30
|
|
|
|
2026-10-26 12:39:08 +05:30
|
|
|
class FreshnessConfidenceScorer:
|
2026-10-22 13:06:10 +05:30
|
|
|
"""
|
2026-10-26 12:39:08 +05:30
|
|
|
Evaluates signal age and applies confidence penalties for stale data.
|
|
|
|
|
Decays confidence by 10% if signal exceeds 14 days old.
|
2026-10-22 13:06:10 +05:30
|
|
|
"""
|
2026-10-22 17:20:44 +05:30
|
|
|
|
|
|
|
|
@staticmethod
|
2026-10-26 12:39:08 +05:30
|
|
|
def calculate_signal_age_days(collected_at: Optional[str]) -> Optional[float]:
|
|
|
|
|
"""Calculates difference in days between current UTC timestamp and collection date."""
|
|
|
|
|
if not collected_at or pd.isna(collected_at) or str(collected_at).strip().lower() in ("nan", "none", ""):
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
# Parse ISO or standard timestamp format
|
|
|
|
|
dt = pd.to_datetime(collected_at, utc=True)
|
|
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
|
delta = now - dt.to_pydatetime()
|
|
|
|
|
return round(max(0.0, delta.total_seconds() / 86400.0), 2)
|
|
|
|
|
except Exception:
|
|
|
|
|
return None
|
2026-10-22 17:20:44 +05:30
|
|
|
|
2026-10-26 12:39:08 +05:30
|
|
|
@classmethod
|
|
|
|
|
def apply_staleness_decay(
|
|
|
|
|
cls,
|
|
|
|
|
base_confidence: float,
|
|
|
|
|
signal_age_days: Optional[float]
|
|
|
|
|
) -> float:
|
|
|
|
|
"""Decays confidence by 10% if age > 14 days; missing age leaves confidence unadjusted."""
|
|
|
|
|
if signal_age_days is None or pd.isna(signal_age_days):
|
|
|
|
|
return base_confidence
|
|
|
|
|
if signal_age_days > 14.0:
|
|
|
|
|
return round(max(0.0, base_confidence * 0.90), 2)
|
|
|
|
|
return round(base_confidence, 2)
|
2026-10-22 17:20:44 +05:30
|
|
|
|
2026-10-26 12:39:08 +05:30
|
|
|
@classmethod
|
|
|
|
|
def enrich_cohort_freshness(cls, df: pd.DataFrame) -> pd.DataFrame:
|
|
|
|
|
"""Adds signal_age_days and updates confidence scores for cohort records."""
|
|
|
|
|
out = df.copy()
|
|
|
|
|
if "collected_at" in out.columns:
|
|
|
|
|
out["signal_age_days"] = out["collected_at"].apply(cls.calculate_signal_age_days)
|
2026-10-22 13:06:10 +05:30
|
|
|
else:
|
2026-10-26 12:39:08 +05:30
|
|
|
out["signal_age_days"] = np.nan
|
2026-10-22 13:06:10 +05:30
|
|
|
|
2026-10-26 12:39:08 +05:30
|
|
|
# Decay instagram_confidence_score where applicable
|
|
|
|
|
if "instagram_confidence_score" in out.columns:
|
|
|
|
|
out["instagram_confidence_score"] = out.apply(
|
|
|
|
|
lambda row: cls.apply_staleness_decay(
|
|
|
|
|
row.get("instagram_confidence_score", 0.0),
|
|
|
|
|
row.get("signal_age_days")
|
|
|
|
|
),
|
|
|
|
|
axis=1
|
|
|
|
|
)
|
|
|
|
|
return out
|