|
|
|
@ -29,14 +29,6 @@ def winsorize(value: float, lower: float = WINSOR_LOWER, upper: float = WINSOR_U
|
|
|
|
return max(lower, min(upper, value))
|
|
|
|
return max(lower, min(upper, value))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_utc(dt) -> datetime:
|
|
|
|
|
|
|
|
"""Ensure a datetime is UTC-aware (SQLite returns naive datetimes)."""
|
|
|
|
|
|
|
|
if dt is None:
|
|
|
|
|
|
|
|
return dt
|
|
|
|
|
|
|
|
if isinstance(dt, datetime) and dt.tzinfo is None:
|
|
|
|
|
|
|
|
return dt.replace(tzinfo=timezone.utc)
|
|
|
|
|
|
|
|
return dt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def compute_zscore(value: float, values: List[float]) -> Optional[float]:
|
|
|
|
def compute_zscore(value: float, values: List[float]) -> Optional[float]:
|
|
|
|
"""Compute z-score of *value* within *values* (requires ≥2 data points)."""
|
|
|
|
"""Compute z-score of *value* within *values* (requires ≥2 data points)."""
|
|
|
|
@ -95,8 +87,8 @@ class FeatureBuilder:
|
|
|
|
|
|
|
|
|
|
|
|
# Filter by symbol
|
|
|
|
# Filter by symbol
|
|
|
|
sym_rows_hist = [r for r in all_rows if symbol in (r.matched_symbols or [])]
|
|
|
|
sym_rows_hist = [r for r in all_rows if symbol in (r.matched_symbols or [])]
|
|
|
|
sym_rows_24h = [r for r in sym_rows_hist if _ensure_utc(r.published_at) >= cutoff_24h]
|
|
|
|
sym_rows_24h = [r for r in sym_rows_hist if r.published_at >= cutoff_24h]
|
|
|
|
sym_rows_6h = [r for r in sym_rows_24h if _ensure_utc(r.published_at) >= cutoff_6h]
|
|
|
|
sym_rows_6h = [r for r in sym_rows_24h if r.published_at >= cutoff_6h]
|
|
|
|
|
|
|
|
|
|
|
|
headline_count_24h = len(sym_rows_24h)
|
|
|
|
headline_count_24h = len(sym_rows_24h)
|
|
|
|
headline_count_6h = len(sym_rows_6h)
|
|
|
|
headline_count_6h = len(sym_rows_6h)
|
|
|
|
@ -144,7 +136,7 @@ class FeatureBuilder:
|
|
|
|
)
|
|
|
|
)
|
|
|
|
all_rows = result.fetchall()
|
|
|
|
all_rows = result.fetchall()
|
|
|
|
sym_rows = [r for r in all_rows if symbol in (r.matched_symbols or [])]
|
|
|
|
sym_rows = [r for r in all_rows if symbol in (r.matched_symbols or [])]
|
|
|
|
sym_rows_24h = [r for r in sym_rows if _ensure_utc(r.published_at) >= cutoff_24h]
|
|
|
|
sym_rows_24h = [r for r in sym_rows if r.published_at >= cutoff_24h]
|
|
|
|
|
|
|
|
|
|
|
|
mentions_24h = len(sym_rows_24h)
|
|
|
|
mentions_24h = len(sym_rows_24h)
|
|
|
|
weighted_views_24h = sum(r.view_count * r.channel_weight for r in sym_rows_24h)
|
|
|
|
weighted_views_24h = sum(r.view_count * r.channel_weight for r in sym_rows_24h)
|
|
|
|
@ -195,7 +187,7 @@ class FeatureBuilder:
|
|
|
|
views_1d = rows[0].views if rows else None
|
|
|
|
views_1d = rows[0].views if rows else None
|
|
|
|
|
|
|
|
|
|
|
|
# 7-day average
|
|
|
|
# 7-day average
|
|
|
|
rows_7d = [r for r in rows if _ensure_utc(r.date) >= cutoff_7d]
|
|
|
|
rows_7d = [r for r in rows if r.date >= cutoff_7d]
|
|
|
|
views_7d_avg = sum(r.views for r in rows_7d) / len(rows_7d) if rows_7d else None
|
|
|
|
views_7d_avg = sum(r.views for r in rows_7d) / len(rows_7d) if rows_7d else None
|
|
|
|
|
|
|
|
|
|
|
|
# Historical z-score
|
|
|
|
# Historical z-score
|
|
|
|
|