""" core/confidence_scorer.py Calculates signal freshness, staleness decay, and multi-platform confidence. Enforces zero-imputation rules across timestamps and verification confidence. """ from datetime import datetime, timezone from typing import Optional import numpy as np import pandas as pd class FreshnessConfidenceScorer: """ Evaluates signal age and applies confidence penalties for stale data. Decays confidence by 10% if signal exceeds 14 days old. """ @staticmethod 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 @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) @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) else: out["signal_age_days"] = np.nan # 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