# `ZenWebsocket.PoolRouter`
[🔗](https://github.com/ZenHive/zen_websocket/blob/v0.9.0/lib/zen_websocket/pool_router.ex#L1)

Health-based connection routing for WebSocket client pools.

Provides connection selection based on health scoring that considers:
- Pending request count (queue depth)
- Response latency (p99)
- Recent error count (with 60s decay)

## Health Score Formula (0-100)

    score = 100 - pending_penalty - latency_penalty - error_penalty

Where:
- `pending_penalty` = min(pending_requests × 10, 40) — 0-40 pts
- `latency_penalty` = min(p99_ms ÷ 25, 30) — 0-30 pts
- `error_penalty` = min(error_count × 15, 20) — 0-20 pts

## ETS Storage

Uses `:zen_websocket_pool` ETS table for:
- Round-robin index for fallback selection
- Per-connection error tracking with 60-second decay

The table is created by a dedicated, lazily spawned owner process. It lives
for the lifetime of the BEAM and therefore survives the process that first
invokes the router.

## Performance Notes

Round-robin selection among equally-healthy connections is O(n) where n is
the number of connections with the same health score. For typical pools
(under 100 connections), this is negligible. If you need larger pools,
consider partitioning into multiple smaller pools.

## Configuration

The error decay period can be configured at compile time:

    config :zen_websocket, :error_decay_ms, 120_000  # 2 minutes

Default is 60 seconds (60,000 ms).

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `pool_health` | 1 | Get health information for all connections in the pool. | `pids: value` |
| `clear_errors` | 1 | Clear the error count for a connection. | `pid: value` |
| `record_error` | 1 | Record an error for a connection (decays after the error window, 60s by default). | `pid: value` |
| `calculate_health` | 1 | Calculate health score (0-100) for a connection. | `pid: value` |
| `select_connection` | 1 | Select the healthiest connection from a pool. | `pids: value` |

# `health_score`

```elixir
@type health_score() :: 0..100
```

# `calculate_health`

```elixir
@spec calculate_health(pid()) :: health_score()
```

Calculates health score (0-100) for a connection.

Gathers metrics from the client and applies the scoring formula.
Returns 100 if metrics cannot be retrieved. That optimistic default is
acceptable for pool routing: unhealthy connections still self-report
via errors.

# `clear_errors`

```elixir
@spec clear_errors(pid()) :: :ok
```

Clears the error count for a connection.

# `pool_health`

```elixir
@spec pool_health([pid()]) :: [%{pid: pid(), health: health_score()}]
```

Returns health information for all connections in the pool.

# `record_error`

```elixir
@spec record_error(pid()) :: :ok
```

Records an error for a connection, incrementing the error count.

Error counts decay after the configured error window (`:error_decay_ms`,
60 seconds by default) with no new errors.

# `select_connection`

```elixir
@spec select_connection([pid()]) :: {:ok, pid()} | {:error, :no_connections}
```

Selects the healthiest connection from a list of client PIDs.

Returns the connection with the highest health score, or falls back
to round-robin selection when health scores are equal.

## Examples

    PoolRouter.select_connection([pid1, pid2, pid3])
    #=> {:ok, pid2}

    PoolRouter.select_connection([])
    #=> {:error, :no_connections}

---

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