"""Event semantics — deterministic, database-free rules shared by the writer and `aia canonicalize events`. Three clocks on every change event: occurred_at = coalesce(effective_at, observed_at) when the thing happened (release date, price effective date) observed_at when a connector saw it recorded_at when the row was written `is_backfill` separates *history being loaded* from *news*: the "+N in 24 h" counters, /changes and the pulse feed only count live (non-backfill) events; timelines and /asof use every event. """ from __future__ import annotations from datetime import datetime, timedelta from typing import Any BACKFILL_LAG_DAYS = 3 RELEASE_LIKE = {"RELEASE", "ANNOUNCEMENT", "NEW_MODEL", "VERSION_RELEASED"} def classify_backfill(event_type: str, effective_at: datetime | None, observed_at: datetime, *, is_first_run: bool = False, entity_first_seen: datetime | None = None) -> bool: """True when the event describes something that happened well before we observed it. * `effective_at` more than BACKFILL_LAG_DAYS before `observed_at` → backfill (an old release date being loaded); * the connector run is the connector's first successful run → backfill (initial corpus, not news); * NEW_* events for an entity whose first-seen hint (release/publication date) predates observation by more than the lag → backfill. """ if is_first_run: return True lag = timedelta(days=BACKFILL_LAG_DAYS) if effective_at is not None and observed_at is not None and _aware(effective_at) < _aware(observed_at) - lag: return True if event_type.startswith("NEW_") and entity_first_seen is not None and observed_at is not None and _aware(entity_first_seen) < _aware(observed_at) - lag: return True return False def group_key_for(event_type: str, entity_id: str | None, effective_at: datetime | None, observed_at: datetime | None) -> str | None: """One release seen in several documents (blog post, docs page, hub listing, provider listing) groups under one key per entity × month.""" if event_type not in RELEASE_LIKE or not entity_id: return None when = effective_at or observed_at if when is None: return None return f"release:{entity_id}:{when.strftime('%Y-%m')}" def _aware(dt: datetime) -> datetime: from datetime import UTC return dt if dt.tzinfo is not None else dt.replace(tzinfo=UTC) # ---------------------------------------------------------------------------------------------- importance (0 minor … 3 major) def _num(v: Any) -> float | None: if isinstance(v, bool): return None if isinstance(v, (int, float)): return float(v) if isinstance(v, str): try: return float(v.replace(",", "")) except ValueError: return None return None def _price_change_ratio(old: Any, new: Any) -> float: """Largest relative move among input/output per-million prices (0 when unknown).""" if not isinstance(old, dict) or not isinstance(new, dict): a, b = _num(old), _num(new) return abs(b - a) / a if a and b is not None and a > 0 else 0.0 best = 0.0 for k in ("input_per_mtok", "output_per_mtok", "cached_input_per_mtok"): a, b = _num(old.get(k)), _num(new.get(k)) if a is not None and b is not None and a > 0: best = max(best, abs(b - a) / a) elif a in (None, 0) and b: best = max(best, 1.0) # newly priced / free → paid counts as a full move return best def importance_for(event_type: str, entity_type: str | None, old: Any = None, new: Any = None, *, tier: int = 2, org_model_count: int = 0, openness: str | None = None, leader_change: bool = False, default: int = 2) -> int: """Deterministic importance: artifact events 0 · model_family events 1 · metadata corrections (PROPERTY_CHANGED) 0 frontier-model release (organisation with ≥ 3 models, source tier ≤ 2) 3 · open-weight release 3 · other NEW_MODEL 2 price change ≥ 50 % → 3, ≥ 20 % → 2, else 1 · context ≥ 5× → 3 (shrink → 1) · deprecation / retirement 3 · benchmark leader change 2 Tier > 2 sources lose one point (never below 0).""" et = event_type.upper() if entity_type == "artifact": return 0 if entity_type == "model_family": return min(1, default) if et == "PROPERTY_CHANGED": return 0 if et == "PRICE_CHANGED": ratio = _price_change_ratio(old, new) imp = 3 if ratio >= 0.5 else 2 if ratio >= 0.2 else 1 elif et == "CONTEXT_CHANGED": a, b = _num(old), _num(new) if a and b and a > 0: imp = 3 if b / a >= 5 else 1 if b < a else 2 else: imp = 2 elif et in ("DEPRECATION_ANNOUNCED", "RETIREMENT_ANNOUNCED"): imp = 3 elif et == "STATUS_CHANGED": imp = 3 if str(new).lower() in ("deprecated", "retired", "discontinued") else 2 elif et == "OPENNESS_CHANGED": imp = 3 if str(new).startswith("open") else 2 elif et == "NEW_MODEL": if openness and str(openness).startswith("open"): imp = 3 elif org_model_count >= 3 and tier <= 2: imp = 3 else: imp = 2 elif et in ("BENCHMARK_LEADER_CHANGED",) or (et.startswith("BENCHMARK") and leader_change): imp = 2 else: imp = default if tier > 2: imp -= 1 return max(0, min(3, imp)) __all__ = ["BACKFILL_LAG_DAYS", "RELEASE_LIKE", "classify_backfill", "group_key_for", "importance_for"]