# Hotness Score

Fuente conceptual: OpenViking `openviking/retrieve/memory_lifecycle.py` (AGPL-3.0)
Reimplementar desde cero. No copiar codigo.

## Funcion a implementar

### hotness_score

- **Dominio:** datascience
- **Lang:** Python
- **Purity:** pure (si se pasa `now` explicitamente)
- **Signature:** `hotness_score(active_count: int, updated_at: datetime | None, now: datetime | None = None, half_life_days: float = 7.0) -> float`
- **Retorno:** float en [0.0, 1.0]
- **Descripcion:** Calcula un score de "hotness" (calor/relevancia) combinando frecuencia de acceso y recencia. Util para ranking de resultados de busqueda, memoria hot/cold, cache eviction.
- **Formula:**
  ```
  score = sigmoid(log1p(active_count)) * time_decay(updated_at)
  ```
- **Algoritmo:**
  1. **Componente de frecuencia:** `freq = 1 / (1 + exp(-log1p(active_count)))` — sigmoid de log1p mapea conteos a (0,1)
  2. **Componente de recencia:** Si updated_at es None → retornar 0.0
     - Normalizar a UTC
     - `age_days = max((now - updated_at).total_seconds() / 86400, 0)`
     - `decay_rate = ln(2) / half_life_days`
     - `recency = exp(-decay_rate * age_days)`
  3. **Score final:** `freq * recency`
- **Propiedades:**
  - active_count=0, cualquier fecha → ~0.5 * recency
  - active_count alto, reciente → cercano a 1.0
  - active_count alto, antiguo → cercano a 0.0
  - updated_at=None → siempre 0.0
  - half_life_days controla velocidad de decaimiento (7 = score se reduce a la mitad cada 7 dias)
- **Deps:** `math`, `datetime`
- **Tests:**
  - active_count=0, updated_at reciente
  - active_count=100, updated_at reciente (score alto)
  - active_count=100, updated_at hace 30 dias (score bajo)
  - updated_at=None (retorna 0.0)
  - now explicito (determinista para tests)
  - half_life_days custom
