# Extractors

Each schema field lists extractors in order; the first that yields a non-empty value wins.

| Kind | Options | Yields |
|---|---|---|
| `header` | `name` | The first value of that header. |
| `mailbox` | `source`: `from`, `to`, `reply_to`; `part`: `address`, `name`, `domain` | That part of the mailbox. |
| `regex` | `pattern` (use a named group `(?<value>…)`, else the first group, else the whole match), `flags` (default `i`), `source` (default `body`) | The captured text, with offsets. |
| `labeled_value` | `labels` (tried longest first), `value` (regex for the value, default rest of line), `source` | The text after `Label:`, `Label #`, or `Label -` at the start of a line. |
| `constant` | `value` | The value. Useful as a last-resort default. |

`source` is one of `subject`, `body`, `from`, `from_name`, `to`, `reply_to`, `attachments`, `all_text`.

## Confidence

Each value's confidence says how much its match is worth, by extractor kind: a labelled value 1, a regex 0.8, a mailbox or header 0.5, a constant 0.3. It is halved when a transform or type coercion fails, and a mailbox name beside no address scores 0. Policy's `auto_accept.min_confidence` compares against it, constants excepted (they are configured defaults, not evidence): 1 admits only labelled matches, 0.8 admits regexes too, 0.5 admits structural fallbacks.

## Lists

A `string[]` field is split on newlines by default, because product names carry commas. Set `split` on the extractor to `commas`, `semicolons`, `lines_or_commas`, or `none`.

## Transforms

`transforms` is an ordered list applied to the extracted text. If one fails, the raw text is kept with confidence 0.5.

| Transform | Effect |
|---|---|
| `"trim"`, `"upper"`, `"lower"`, `"collapse_whitespace"`, `"first_line"` | As named. |
| `"number"`, `"integer"` | Parses `$1,234.50`, `(12)`, `1 234`. |
| `"date"` | Parses `2026-09-19`, `9/19/2026`, `September 19, 2026`, `19 Sep 2026` to `YYYY-MM-DD`. Numeric dates are read month first; use a regex extractor to reorder day-first sources. |
| `{"replace": {"pattern": "regex", "with": "text"}}` | Global, case-insensitive, multiline replace. |
| `{"default": value}` | Substitutes when the value is empty. |

Type coercion runs after transforms based on the field's `type`, so a `date` field needs no explicit `"date"` transform.

## Coordinates

Each value's `source` records `source`, `documentId` (for attachments), `start` and `end` offsets into that text, and `extractorIndex` (which extractor in the list produced it).
