# at.tensei.mapping

> Published by [tensei.at](https://lexicon.garden/identity/did:plc:7fk7ea7vlnlpzgy225x66vro)

✓ This is the authoritative definition for this NSID.

## Description

A Tensei mapping, version 1 (spec §4): records of one source collection mapped into output fields the user authors. Built-in presets are starting points, never a contract: any field name is allowed, and a field is mandatory only when the user marks it `required`. The atproto data model has integers only, so a number in the record is an integer written as it is, or any other number in fixed point, the value times 10⁶ (spec §1.5): a bare integer where this lexicon declares it — a #mappingField's `confidence` and a #scale's `min` and `max` — and a #fixedPoint everywhere else. Where the lexicon language cannot state a rule the version-1 validator enforces, the property is left permissive here and the rule is named in its description.

## Links

- [View on Lexicon Garden](https://lexicon.garden/lexicon/did:plc:7fk7ea7vlnlpzgy225x66vro/at.tensei.mapping)
- [Documentation](https://lexicon.garden/lexicon/did:plc:7fk7ea7vlnlpzgy225x66vro/at.tensei.mapping/docs)
- [Examples](https://lexicon.garden/lexicon/did:plc:7fk7ea7vlnlpzgy225x66vro/at.tensei.mapping/examples)

## Definitions

### `at.tensei.mapping#fieldDefinition`

**Type**: `object`

A field's declared type and authored requiredness (§3.1). `required` is always written; validation supplies no default.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `required` | `boolean` | Yes |  |
| `type` | `ref` → `#fieldType` | Yes |  |

### `at.tensei.mapping#fieldType`

**Type**: `object`

A declared output type (§3.1). `namespace` is required when `kind` is key and names the key namespace (§3.6).

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `kind` | `string` | Yes |  |
| `namespace` | `ref` → `#keyNamespace` | No |  |

### `at.tensei.mapping#fixedPoint`

**Type**: `object`

A number that is not an integer, where no type is declared for it (§1.5): `value` is the number times 10⁶, rounded to six decimal places, so 0.4 is {value: 400000}. An integer is never written this way; it is written as it is. Wherever `$type` names this def, the object is that number. Not stated by the lexicon, and enforced by the reader: it holds no property but `$type` and `value`.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `value` | `integer` | Yes |  |

### `at.tensei.mapping#guard`

**Type**: `object`

A §5 leaf. `field` is a §4.2 source path. `value` is a scalar (a string, an integer, a #fixedPoint for any other number, or a boolean; never null or an object), or for in/notIn an array of scalars, and is absent for exists/notExists; the lexicon language has no type for a scalar of any kind, so `value` is not declared.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes |  |
| `op` | `ref` → `#predicateOp` | Yes |  |

### `at.tensei.mapping#keyNamespace`

**Type**: `string`

**Allowed Values**:
- `isbn`
- `olwork`
- `goodreads`
- `hive`
- `tmdb`
- `url`

### `at.tensei.mapping`

**Type**: `record`

One source collection → user-shaped output (§4.1). Also defines the field, expression and presentation types that at.tensei.pipeline refers to.

**Key**: `tid`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `createdAt` | `string` | No | When the mapping was created: a datetime as §3.1 reads one, which accepts forms atproto's `datetime` format does not, so the format is not declared. |
| `guards` | `array` | No | §5 predicate leaves over source paths (§4.1); no all/any/not groups. |
| `origin` | `ref` → `#origin` | No | Present on an adopted copy: the exact version copied (§1.3). Attribution and update-check input; execution never reads it. |
| `presentation` | `ref` → `#presentation` | No | For a standalone preview and to keep a migrated draft's choices; a pipeline's own presentation is authoritative after downstream edits (§3.8). Its entries must name fields in slots. |
| `schema` | `ref` → `#schemaPin` | No |  |
| `slots` | `unknown` | Yes | The output fields: an object keyed by field names the user chooses, any non-empty name and not limited to a preset's, each value a #mappingField. The lexicon language has no map type, so the names and values are stated here rather than declared. |
| `source` | `string` (nsid) | Yes | The NSID of the collection this mapping reads, and the only place its field names appear. |
| `version` | `integer` | Yes | The document version. A document without one is the Phase-2 fixed-preset form, which is migrated explicitly to version 1 (§8.3) and never published as it is. |
| `viewType` | `string` | No | The built-in preset this mapping started from, as attribution only. It never adds, limits or requires a field (§3.8). |

### `at.tensei.mapping#mappingExpression`

**Type**: `object`

A source-mapping expression (§4.3): path {path}, constant {value}, operation {op, inputs, args?} or lookup {input, from, to}. Not stated by the lexicon: which properties each kind requires; `value`, any JSON value, in which a number that is not an integer is a #fixedPoint; `args`, whose count and types each operation fixes (a string separator for split, join and concat, a string find and replacement for replace, an optional string format for parseDate, a non-zero divisor for divide, none otherwise; a number that is not an integer is a #fixedPoint); the input count (one, or one or more for coalesce and concat); §4.2 path syntax; and a nesting depth of at most 64 levels.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `from` | `ref` → `#keyNamespace` | No |  |
| `input` | `ref` → `#mappingExpression` | No |  |
| `inputs` | `array` | No |  |
| `kind` | `string` | Yes |  |
| `op` | `ref` → `#operationName` | No |  |
| `path` | `string` | No |  |
| `to` | `ref` → `#keyNamespace` | No |  |

### `at.tensei.mapping#mappingField`

**Type**: `object`

One output field of a mapping (§4.1): a #fieldDefinition plus how this source fills it. An unmapped field has no `expr`.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `by` | `string` | No |  |
| `confidence` | `integer` | No | Proposal metadata: a number from 0 to 1, in fixed point, the value times 10⁶ (§1.5), as a bare integer: 0.4 is 400000. |
| `expr` | `ref` → `#mappingExpression` | No |  |
| `normalization` | `ref` → `#normalization` | No |  |
| `required` | `boolean` | Yes |  |
| `type` | `ref` → `#fieldType` | Yes |  |

### `at.tensei.mapping#normalization`

**Type**: `object`

Source-specific input normalization (§4.1), not part of the shared field type. A mapped score field needs {kind: score, scale}, a mapped media field {kind: media, mediaKind}, and canonical {namespace} belongs on a uri field only.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `kind` | `string` | Yes |  |
| `mediaKind` | `string` | No |  |
| `namespace` | `ref` → `#keyNamespace` | No |  |
| `scale` | `ref` → `#scale` | No |  |

### `at.tensei.mapping#operationName`

**Type**: `string`

§4.3's closed list of operations. A new operation is a spec change, not an extension point.

**Allowed Values**:
- `split`
- `join`
- `wrap`
- `first`
- `trim`
- `lower`
- `replace`
- `divide`
- `parseDate`
- `coalesce`
- `concat`

### `at.tensei.mapping#origin`

**Type**: `object`

The original record an independent copy was made from (§1.3, §8.3).

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `cid` | `string` (cid) | Yes | The CID of the exact version copied. The `cid` format accepts any spelling of a CID; the validator accepts only the form atproto writes: version 1, in base32, over a SHA-256 digest. |
| `uri` | `string` (at-uri) | Yes | The record copied. Not stated by the lexicon, and enforced by the validator: exactly at://<DID>/<collection NSID>/<record key>, with a DID rather than a handle for its authority. |

### `at.tensei.mapping#predicateOp`

**Type**: `string`

§5's closed list of operators.

**Allowed Values**:
- `eq`
- `ne`
- `lt`
- `lte`
- `gt`
- `gte`
- `contains`
- `notContains`
- `startsWith`
- `matches`
- `exists`
- `notExists`
- `in`
- `notIn`

### `at.tensei.mapping#presentation`

**Type**: `object`

How the output is shown (§3.8); it never changes data or requiredness. Entries are unique by `field`, each names a field of the shape it presents, and `display` is allowed on a score field only.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `fields` | `array` | Yes |  |
| `layout` | `string` | Yes |  |

### `at.tensei.mapping#presentationField`

**Type**: `object`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `display` | `string` | No |  |
| `field` | `string` | Yes |  |
| `label` | `string` | No |  |
| `visible` | `boolean` | Yes |  |

### `at.tensei.mapping#scale`

**Type**: `object`

A source's score scale (§3.4), each bound in fixed point, the value times 10⁶ (§1.5), as a bare integer: a scale of 1 to 10 is 1000000 to 10000000. Not stated by the lexicon, and enforced by the validator: min < max.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `max` | `integer` | Yes |  |
| `min` | `integer` | Yes |  |

### `at.tensei.mapping#schemaPin`

**Type**: `object`

The schema version the mapping was written against (§4.1, §11). Not stated by the lexicon, and enforced by the validator: `nsid` is the mapping's `source`; a published pin has `cid` and no `hash`, an inferred one `hash` and no `cid`.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `cid` | `string` (cid) | No | A published pin's version: the CID of the lexicon record. The `cid` format accepts any spelling of a CID; the validator accepts only the form atproto writes: version 1, in base32, over a SHA-256 digest. |
| `hash` | `string` | No | An inferred pin's version: `sha256:` and 64 lowercase hex digits, a form the lexicon language cannot state. |
| `nsid` | `string` (nsid) | Yes |  |
| `provenance` | `string` | Yes |  |
| `resolvedAt` | `string` | No | When the version was resolved: a datetime as §3.1 reads one, which accepts forms atproto's `datetime` format does not, so the format is not declared. |

## Raw Schema

```json
{
  "$type": "com.atproto.lexicon.schema",
  "defs": {
    "fieldDefinition": {
      "description": "A field's declared type and authored requiredness (§3.1). `required` is always written; validation supplies no default.",
      "properties": {
        "required": {
          "type": "boolean"
        },
        "type": {
          "ref": "#fieldType",
          "type": "ref"
        }
      },
      "required": [
        "type",
        "required"
      ],
      "type": "object"
    },
    "fieldType": {
      "description": "A declared output type (§3.1). `namespace` is required when `kind` is key and names the key namespace (§3.6).",
      "properties": {
        "kind": {
          "enum": [
            "text",
            "text[]",
            "number",
            "bool",
            "datetime",
            "uri",
            "did",
            "ref",
            "media",
            "score",
            "key",
            "json"
          ],
          "type": "string"
        },
        "namespace": {
          "ref": "#keyNamespace",
          "type": "ref"
        }
      },
      "required": [
        "kind"
      ],
      "type": "object"
    },
    "fixedPoint": {
      "description": "A number that is not an integer, where no type is declared for it (§1.5): `value` is the number times 10⁶, rounded to six decimal places, so 0.4 is {value: 400000}. An integer is never written this way; it is written as it is. Wherever `$type` names this def, the object is that number. Not stated by the lexicon, and enforced by the reader: it holds no property but `$type` and `value`.",
      "properties": {
        "value": {
          "maximum": 999999999999999,
          "minimum": -999999999999999,
          "type": "integer"
        }
      },
      "required": [
        "value"
      ],
      "type": "object"
    },
    "guard": {
      "description": "A §5 leaf. `field` is a §4.2 source path. `value` is a scalar (a string, an integer, a #fixedPoint for any other number, or a boolean; never null or an object), or for in/notIn an array of scalars, and is absent for exists/notExists; the lexicon language has no type for a scalar of any kind, so `value` is not declared.",
      "properties": {
        "field": {
          "minLength": 1,
          "type": "string"
        },
        "op": {
          "ref": "#predicateOp",
          "type": "ref"
        }
      },
      "required": [
        "field",
        "op"
      ],
      "type": "object"
    },
    "keyNamespace": {
      "enum": [
        "isbn",
        "olwork",
        "goodreads",
        "hive",
        "tmdb",
        "url"
      ],
      "type": "string"
    },
    "main": {
      "description": "One source collection → user-shaped output (§4.1). Also defines the field, expression and presentation types that at.tensei.pipeline refers to.",
      "key": "tid",
      "record": {
        "properties": {
          "createdAt": {
            "description": "When the mapping was created: a datetime as §3.1 reads one, which accepts forms atproto's `datetime` format does not, so the format is not declared.",
            "type": "string"
          },
          "guards": {
            "description": "§5 predicate leaves over source paths (§4.1); no all/any/not groups.",
            "items": {
              "ref": "#guard",
              "type": "ref"
            },
            "type": "array"
          },
          "origin": {
            "description": "Present on an adopted copy: the exact version copied (§1.3). Attribution and update-check input; execution never reads it.",
            "ref": "#origin",
            "type": "ref"
          },
          "presentation": {
            "description": "For a standalone preview and to keep a migrated draft's choices; a pipeline's own presentation is authoritative after downstream edits (§3.8). Its entries must name fields in slots.",
            "ref": "#presentation",
            "type": "ref"
          },
          "schema": {
            "ref": "#schemaPin",
            "type": "ref"
          },
          "slots": {
            "description": "The output fields: an object keyed by field names the user chooses, any non-empty name and not limited to a preset's, each value a #mappingField. The lexicon language has no map type, so the names and values are stated here rather than declared.",
            "type": "unknown"
          },
          "source": {
            "description": "The NSID of the collection this mapping reads, and the only place its field names appear.",
            "format": "nsid",
            "type": "string"
          },
          "version": {
            "const": 1,
            "description": "The document version. A document without one is the Phase-2 fixed-preset form, which is migrated explicitly to version 1 (§8.3) and never published as it is.",
            "type": "integer"
          },
          "viewType": {
            "description": "The built-in preset this mapping started from, as attribution only. It never adds, limits or requires a field (§3.8).",
            "enum": [
              "review",
              "bookmark"
            ],
            "type": "string"
          }
        },
        "required": [
          "version",
          "source",
          "slots"
        ],
        "type": "object"
      },
      "type": "record"
    },
    "mappingExpression": {
      "description": "A source-mapping expression (§4.3): path {path}, constant {value}, operation {op, inputs, args?} or lookup {input, from, to}. Not stated by the lexicon: which properties each kind requires; `value`, any JSON value, in which a number that is not an integer is a #fixedPoint; `args`, whose count and types each operation fixes (a string separator for split, join and concat, a string find and replacement for replace, an optional string format for parseDate, a non-zero divisor for divide, none otherwise; a number that is not an integer is a #fixedPoint); the input count (one, or one or more for coalesce and concat); §4.2 path syntax; and a nesting depth of at most 64 levels.",
      "properties": {
        "from": {
          "ref": "#keyNamespace",
          "type": "ref"
        },
        "input": {
          "ref": "#mappingExpression",
          "type": "ref"
        },
        "inputs": {
          "items": {
            "ref": "#mappingExpression",
            "type": "ref"
          },
          "type": "array"
        },
        "kind": {
          "enum": [
            "path",
            "constant",
            "operation",
            "lookup"
          ],
          "type": "string"
        },
        "op": {
          "ref": "#operationName",
          "type": "ref"
        },
        "path": {
          "minLength": 1,
          "type": "string"
        },
        "to": {
          "ref": "#keyNamespace",
          "type": "ref"
        }
      },
      "required": [
        "kind"
      ],
      "type": "object"
    },
    "mappingField": {
      "description": "One output field of a mapping (§4.1): a #fieldDefinition plus how this source fills it. An unmapped field has no `expr`.",
      "properties": {
        "by": {
          "enum": [
            "constraint",
            "name",
            "stats",
            "model",
            "human"
          ],
          "type": "string"
        },
        "confidence": {
          "description": "Proposal metadata: a number from 0 to 1, in fixed point, the value times 10⁶ (§1.5), as a bare integer: 0.4 is 400000.",
          "maximum": 1000000,
          "minimum": 0,
          "type": "integer"
        },
        "expr": {
          "ref": "#mappingExpression",
          "type": "ref"
        },
        "normalization": {
          "ref": "#normalization",
          "type": "ref"
        },
        "required": {
          "type": "boolean"
        },
        "type": {
          "ref": "#fieldType",
          "type": "ref"
        }
      },
      "required": [
        "type",
        "required"
      ],
      "type": "object"
    },
    "normalization": {
      "description": "Source-specific input normalization (§4.1), not part of the shared field type. A mapped score field needs {kind: score, scale}, a mapped media field {kind: media, mediaKind}, and canonical {namespace} belongs on a uri field only.",
      "properties": {
        "kind": {
          "enum": [
            "score",
            "media",
            "canonical"
          ],
          "type": "string"
        },
        "mediaKind": {
          "enum": [
            "blob",
            "url"
          ],
          "type": "string"
        },
        "namespace": {
          "ref": "#keyNamespace",
          "type": "ref"
        },
        "scale": {
          "ref": "#scale",
          "type": "ref"
        }
      },
      "required": [
        "kind"
      ],
      "type": "object"
    },
    "operationName": {
      "description": "§4.3's closed list of operations. A new operation is a spec change, not an extension point.",
      "enum": [
        "split",
        "join",
        "wrap",
        "first",
        "trim",
        "lower",
        "replace",
        "divide",
        "parseDate",
        "coalesce",
        "concat"
      ],
      "type": "string"
    },
    "origin": {
      "description": "The original record an independent copy was made from (§1.3, §8.3).",
      "properties": {
        "cid": {
          "description": "The CID of the exact version copied. The `cid` format accepts any spelling of a CID; the validator accepts only the form atproto writes: version 1, in base32, over a SHA-256 digest.",
          "format": "cid",
          "type": "string"
        },
        "uri": {
          "description": "The record copied. Not stated by the lexicon, and enforced by the validator: exactly at://<DID>/<collection NSID>/<record key>, with a DID rather than a handle for its authority.",
          "format": "at-uri",
          "type": "string"
        }
      },
      "required": [
        "uri",
        "cid"
      ],
      "type": "object"
    },
    "predicateOp": {
      "description": "§5's closed list of operators.",
      "enum": [
        "eq",
        "ne",
        "lt",
        "lte",
        "gt",
        "gte",
        "contains",
        "notContains",
        "startsWith",
        "matches",
        "exists",
        "notExists",
        "in",
        "notIn"
      ],
      "type": "string"
    },
    "presentation": {
      "description": "How the output is shown (§3.8); it never changes data or requiredness. Entries are unique by `field`, each names a field of the shape it presents, and `display` is allowed on a score field only.",
      "properties": {
        "fields": {
          "items": {
            "ref": "#presentationField",
            "type": "ref"
          },
          "type": "array"
        },
        "layout": {
          "enum": [
            "list",
            "table",
            "review",
            "bookmark"
          ],
          "type": "string"
        }
      },
      "required": [
        "layout",
        "fields"
      ],
      "type": "object"
    },
    "presentationField": {
      "properties": {
        "display": {
          "enum": [
            "stars5",
            "stars10",
            "percent",
            "raw"
          ],
          "type": "string"
        },
        "field": {
          "minLength": 1,
          "type": "string"
        },
        "label": {
          "type": "string"
        },
        "visible": {
          "type": "boolean"
        }
      },
      "required": [
        "field",
        "visible"
      ],
      "type": "object"
    },
    "scale": {
      "description": "A source's score scale (§3.4), each bound in fixed point, the value times 10⁶ (§1.5), as a bare integer: a scale of 1 to 10 is 1000000 to 10000000. Not stated by the lexicon, and enforced by the validator: min < max.",
      "properties": {
        "max": {
          "maximum": 999999999999999,
          "minimum": -999999999999999,
          "type": "integer"
        },
        "min": {
          "maximum": 999999999999999,
          "minimum": -999999999999999,
          "type": "integer"
        }
      },
      "required": [
        "min",
        "max"
      ],
      "type": "object"
    },
    "schemaPin": {
      "description": "The schema version the mapping was written against (§4.1, §11). Not stated by the lexicon, and enforced by the validator: `nsid` is the mapping's `source`; a published pin has `cid` and no `hash`, an inferred one `hash` and no `cid`.",
      "properties": {
        "cid": {
          "description": "A published pin's version: the CID of the lexicon record. The `cid` format accepts any spelling of a CID; the validator accepts only the form atproto writes: version 1, in base32, over a SHA-256 digest.",
          "format": "cid",
          "type": "string"
        },
        "hash": {
          "description": "An inferred pin's version: `sha256:` and 64 lowercase hex digits, a form the lexicon language cannot state.",
          "minLength": 1,
          "type": "string"
        },
        "nsid": {
          "format": "nsid",
          "type": "string"
        },
        "provenance": {
          "enum": [
            "published",
            "inferred"
          ],
          "type": "string"
        },
        "resolvedAt": {
          "description": "When the version was resolved: a datetime as §3.1 reads one, which accepts forms atproto's `datetime` format does not, so the format is not declared.",
          "type": "string"
        }
      },
      "required": [
        "nsid",
        "provenance"
      ],
      "type": "object"
    }
  },
  "description": "A Tensei mapping, version 1 (spec §4): records of one source collection mapped into output fields the user authors. Built-in presets are starting points, never a contract: any field name is allowed, and a field is mandatory only when the user marks it `required`. The atproto data model has integers only, so a number in the record is an integer written as it is, or any other number in fixed point, the value times 10⁶ (spec §1.5): a bare integer where this lexicon declares it — a #mappingField's `confidence` and a #scale's `min` and `max` — and a #fixedPoint everywhere else. Where the lexicon language cannot state a rule the version-1 validator enforces, the property is left permissive here and the rule is named in its description.",
  "id": "at.tensei.mapping",
  "lexicon": 1
}
```
