# `BB.Estimator.Server`
[🔗](https://github.com/beam-bots/bb/blob/main/lib/bb/estimator/server.ex#L5)

Wrapper GenServer for estimator callback modules.

Responsibilities:

- Resolves parameter references in opts at startup and on parameter
  changes (mirroring `BB.Sensor.Server` / `BB.Controller.Server`).
- Subscribes to the estimator's declared input paths.
- Dispatches incoming input messages to the callback module via
  `c:BB.Estimator.handle_input/2`. Single-input estimators receive the
  bare `%BB.Message{}`; multi-input estimators receive a
  `%{input_name => %BB.Message{}}` map gathered when the driver input
  arrives.
- For multi-input estimators, enforces `sync_tolerance`: if any
  non-driver input is stale relative to the driver by more than the
  configured tolerance, the dispatch is dropped (with
  `[:bb, :estimator, :dropped]` telemetry) instead of fired with a stale
  snapshot.
- Publishes each `{output_name, message}` returned from a callback's
  `{:reply, outputs, state}` reply to that output's configured path.
- Emits `:input`, `:output`, `:latency`, and `:dropped` telemetry.

Health transitions, lost-detection, and `on_degraded` / `on_lost` /
`on_recovered` command dispatch are Phase 2 and not handled here yet.

## Init args

The framework supplies the following keys in the start-link init arg.
Internal keys (double-underscored) are stripped by the server before
calling `c:BB.Estimator.init/1`; public keys (`:bb`,
`:estimator_context`) are passed through unchanged so user code can
read them.

- `:__callback_module__` - the user's estimator module.
- `:__estimator_inputs__` - `%{mode: :single | :multi, inputs: [...],
  sync_tolerance_ns: integer() | nil}` describing the input wiring.
- `:__estimator_outputs__` - `%{output_name => [atom()]}` mapping output
  names to their full pubsub paths.
- `:bb` - `%{robot: module, path: [atom]}`, the per-process context.
- `:estimator_context` - the `BB.Estimator.Context.t()` exposed to the
  callback module's `init/1`.

Plus any user options declared via the estimator's `options_schema/0`.

# `health_state`

```elixir
@type health_state() :: :healthy | :degraded | :lost
```

# `input_spec`

```elixir
@type input_spec() :: %{name: atom(), path: [atom()], driver?: boolean()}
```

# `t`

```elixir
@type t() :: %BB.Estimator.Server{
  bb: %{robot: module(), path: [atom()]},
  callback_module: module(),
  consecutive_ok: non_neg_integer(),
  context: BB.Estimator.Context.t(),
  driver_input: atom() | nil,
  health_state: health_state(),
  input_name_by_path: %{required([atom()]) =&gt; atom()},
  inputs: [input_spec()],
  last_messages: %{required(atom()) =&gt; BB.Message.t()},
  latency_budget_ns: integer() | nil,
  lost_after_ns: integer() | nil,
  lost_timer_ref: reference() | nil,
  mode: :single | :multi,
  on_degraded: atom() | nil,
  on_lost: atom() | nil,
  on_recovered: atom() | nil,
  outputs: %{required(atom()) =&gt; [atom()]},
  param_subscriptions: %{required([atom()]) =&gt; atom()},
  raw_opts: keyword(),
  recover_after: pos_integer(),
  resolved_opts: keyword(),
  sync_tolerance_ns: integer() | nil,
  user_state: term()
}
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

---

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