# `PtcRunner.SubAgent.Signature.Coercion`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/sub_agent/signature/coercion.ex#L1)

Coerce values to expected types with warning generation.

This module handles lenient input validation for LLMs, which sometimes
produce slightly malformed data (e.g., quoted numbers, missing types).

## Elixir to Signature Type Mapping

### Primitive Types

| Elixir Type | Signature Type | Notes |
|-------------|----------------|-------|
| `String.t()` | `:string` | UTF-8 strings |
| `binary()` | `:string` | Same as String.t() |
| `integer()` | `:int` | Whole numbers |
| `non_neg_integer()` | `:int` | Validation can enforce >= 0 |
| `pos_integer()` | `:int` | Validation can enforce > 0 |
| `float()` | `:float` | Decimal numbers |
| `number()` | `:float` | Accepts int or float |
| `boolean()` | `:bool` | Boolean values |
| `atom()` | `:keyword` | Atoms as keywords |
| `any()` | `:any` | Matches everything |

### Collection Types

| Elixir Type | Signature Type | Notes |
|-------------|----------------|-------|
| `list(t)` | `[:t]` | Homogeneous lists |
| `map()` | `:map` | Untyped dictionary |
| `%{key: type}` | `{:key :type}` | Typed map |

### Special Types

| Elixir Type | Signature Type | Rationale |
|-------------|----------------|-----------|
| `DateTime.t()` | `:string` | ISO 8601 format, LLM-friendly |
| `t | nil` | `:t?` | Optional via `?` suffix |

## Coercion Rules

LLMs sometimes produce slightly malformed data. Input coercion handles common cases:

| From | To | Behavior | Warning |
|------|-----|----------|---------|
| `"42"` | `:int` | `42` | Yes |
| `"3.14"` | `:float` | `3.14` | Yes |
| `"-5"` | `:int` | `-5` | Yes |
| `"true"` | `:bool` | `true` | Yes |
| `"false"` | `:bool` | `false` | Yes |
| `42` | `:float` | `42.0` | No (silent widening) |
| `42.0` | `:int` | Error | - |
| `"hello"` | `:int` | Error | - |
| `:atom` | `:string` | `"atom"` | Yes |
| `"atom"` | `:keyword` | `:atom` | Yes |

Output validation is strict - no coercion applied.

## Coercion Modes

| Mode | Input Coercion | Output Validation | Use Case |
|------|----------------|-------------------|----------|
| `:enabled` (default) | Apply with warnings | Strict | Production |
| `:warn_only` | Apply with warnings | Log warnings only | Development |
| `:strict` | No coercion | Strict, reject extra fields | Testing |
| `:disabled` | Skip | Skip | Debugging |

## Examples

    iex> PtcRunner.SubAgent.Signature.Coercion.coerce("42", :int)
    {:ok, 42, ["coerced string \"42\" to integer"]}

    iex> PtcRunner.SubAgent.Signature.Coercion.coerce(42, :float)
    {:ok, 42.0, []}

    iex> PtcRunner.SubAgent.Signature.Coercion.coerce("hello", :int)
    {:error, "cannot coerce string \"hello\" to integer"}

    iex> PtcRunner.SubAgent.Signature.Coercion.coerce("hello", :keyword)
    {:ok, :hello, ["coerced string \"hello\" to keyword"]}

# `coercion_result`

```elixir
@type coercion_result() :: {:ok, term(), [String.t()]} | {:error, String.t()}
```

# `coerce`

```elixir
@spec coerce(term(), atom() | tuple()) :: coercion_result()
```

Coerce a value to the expected type.

Returns `{:ok, coerced_value, warnings}` or `{:error, reason}`.

## Examples

    iex> PtcRunner.SubAgent.Signature.Coercion.coerce("42", :int)
    {:ok, 42, ["coerced string \"42\" to integer"]}

    iex> PtcRunner.SubAgent.Signature.Coercion.coerce(42, :float)
    {:ok, 42.0, []}

    iex> PtcRunner.SubAgent.Signature.Coercion.coerce("hello", :int)
    {:error, "cannot coerce string \"hello\" to integer"}

    iex> PtcRunner.SubAgent.Signature.Coercion.coerce(%{"id" => "42", "name" => "Alice"}, {:map, [{"id", :int}, {"name", :string}]})
    {:ok, %{"id" => 42, "name" => "Alice"}, ["coerced string \"42\" to integer"]}

# `coerce`

```elixir
@spec coerce(term(), atom() | tuple(), keyword()) :: coercion_result()
```

Coerce a value to the expected type with options.

Options:
- `:nested` - whether this is a nested coercion (default: false)

Returns `{:ok, coerced_value, warnings}` or `{:error, reason}`.

---

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