# `ZenQuant.WS`
[🔗](https://github.com/ZenHive/zen_quant/blob/v0.8.1/lib/zen_quant/ws.ex#L1)

WebSocket health monitoring analytics.

Pure functions for assessing WebSocket connection health from
timestamps and metrics. Designed to work with ZenWebsocket's
`get_state_metrics/1` output but accepts any map with the expected fields.

## Example

    last_update = DateTime.add(DateTime.utc_now(), -120, :second)
    ZenQuant.WS.stale_check(last_update, threshold_seconds: 60)
    # => %{stale: true, age_seconds: 120, threshold_seconds: 60}

    metrics = %{
      connection_state: :connected,
      heartbeat_timer_active: true,
      heartbeat_failures: 0,
      last_heartbeat_at: DateTime.utc_now()
    }
    ZenQuant.WS.reconnect_metrics(metrics)
    # => %{healthy: true, heartbeat_age_seconds: 0, ...original fields...}

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `reconnect_metrics` | 2 | Compute derived health analytics from ZenWebsocket state metrics. | `state_metrics: exchange_data` |
| `stale_check` | 2 | Check if a WebSocket data stream is stale based on last update time. | `last_update: value` |

# `stale_result`

```elixir
@type stale_result() :: %{
  stale: boolean(),
  age_seconds: integer(),
  threshold_seconds: pos_integer()
}
```

Staleness check result with age and threshold context

# `reconnect_metrics`

```elixir
@spec reconnect_metrics(
  map(),
  keyword()
) :: map()
```

Compute derived health analytics from ZenWebsocket state metrics.

## Parameters

  * `state_metrics` - Map with :connection_state, :heartbeat_timer_active, :heartbeat_failures, :last_heartbeat_at (exchange_data)

## Options

  * `now` - Current time for age calculation (default: `"DateTime.utc_now()"`)

## Returns

Original fields plus :heartbeat_age_seconds and :healthy (boolean) (`map`)

### Example

```elixir
%{
  last_heartbeat_at: ~U[2026-02-25 12:00:00Z],
  heartbeat_failures: 0,
  connection_state: :connected,
  heartbeat_timer_active: true,
  healthy: true,
  heartbeat_age_seconds: 3
}
```

```elixir
# descripex:contract
%{
  opts: %{
    now: %{
      default: "DateTime.utc_now()",
      type: :datetime,
      description: "Current time for age calculation"
    }
  },
  params: %{
    state_metrics: %{
      description: "Map with :connection_state, :heartbeat_timer_active, :heartbeat_failures, :last_heartbeat_at",
      source: "ZenWebsocket.Client.get_state_metrics/1",
      kind: :exchange_data
    }
  },
  returns: %{
    type: :map,
    description: "Original fields plus :heartbeat_age_seconds and :healthy (boolean)"
  },
  returns_example: %{
    last_heartbeat_at: ~U[2026-02-25 12:00:00Z],
    heartbeat_failures: 0,
    connection_state: :connected,
    heartbeat_timer_active: true,
    healthy: true,
    heartbeat_age_seconds: 3
  }
}
```

# `stale_check`

```elixir
@spec stale_check(
  DateTime.t(),
  keyword()
) :: stale_result()
```

Check if a WebSocket data stream is stale based on last update time.

## Parameters

  * `last_update` - DateTime of the last received update (value)

## Options

  * `threshold_seconds` - Staleness threshold in seconds (default: `60`)
  * `now` - Current time for comparison (default: `"DateTime.utc_now()"`)

## Returns

Map with :stale (boolean), :age_seconds, :threshold_seconds (`map`)

### Example

```elixir
%{stale: true, age_seconds: 120, threshold_seconds: 60}
```

```elixir
# descripex:contract
%{
  opts: %{
    now: %{
      default: "DateTime.utc_now()",
      type: :datetime,
      description: "Current time for comparison"
    },
    threshold_seconds: %{
      default: 60,
      type: :integer,
      description: "Staleness threshold in seconds"
    }
  },
  params: %{
    last_update: %{
      description: "DateTime of the last received update",
      kind: :value
    }
  },
  returns: %{
    type: :map,
    description: "Map with :stale (boolean), :age_seconds, :threshold_seconds"
  },
  returns_example: %{stale: true, age_seconds: 120, threshold_seconds: 60}
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
