---
title: "Model specification"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Model specification}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

See [Authoring model definitions](model-authoring.html) to create your first model.

## Field types and constraints

Every field declares a `type`:

- `string`: text.
- `email`: text matching `^[^\s@]+@[^\s@]+\.[^\s@]+$`; mailbox existence is not checked.
- `integer`: a finite number without a fractional part.
- `number`: a finite number.
- `boolean`: `true` or `false`.
- `latitude`: a finite number in decimal degrees, from -90 to 90 inclusive.
- `longitude`: a finite number in decimal degrees, from -180 to 180 inclusive.
- `date`: a valid date string in `YYYY-MM-DD` format.
- `datetime`: a valid UTC datetime string in `YYYY-MM-DDTHH:MM:SSZ` format.

Coordinate types already include their geographic bounds. Missing values are governed by requiredness.

Optional constraints are:

- `mandatory`: requires a filled value; omitted means `false`.
- `required_field`: requires a value when the named field on the same sheet is filled.
- `enum`: a nonempty list of distinct, non-null values matching the type, bounds and pattern.
- `minimum`, `maximum`: inclusive numeric bounds; integer bounds must be whole numbers.
- `maximum_field`: the value must not exceed the named numeric field on the same sheet; compare only valid, present values.
- `pattern`: a PCRE regular expression for string, email, date or datetime fields.
- `sequence`: consecutive integer numbering in worksheet order, grouped by same-sheet fields.
  Declare `sequence: {start: 1, group_by: [trip_id, catch_id]}` on an integer or string table field.
- `unique`: requires distinct values within a repeated table field.
- `reference`: a catalogue, table and named lookup.

Enums are constraints, not a separate type. Quote numeric-looking string codes: `enum: ["01", "02"]`.

For conditional fields, `required_field: length` makes the unit required when length is filled, including zero.

In tables, this applies within the same row. A field cannot depend on itself; mutual dependencies are allowed.

The condition still permits a unit when length is empty.

Reference bindings default to error severity. Use `severity: warning` for a warning.

Declare tables under freely named catalogues:

```yaml
references:
  people: [contacts]
```

Bind the field to a table and lookup:

```yaml
contact_email: {location: C4, type: email, reference: {catalogue: people, table: contacts, lookup: email, severity: warning}}
```

Catalogue names have no built-in meaning. Matching settings and reference data belong outside the model.

See `?form_fields` for the effective field properties returned by accessors.

## Workbook layouts

`sheets` is an ordered mapping of worksheet names. Every declared sheet is required; names are unique ignoring case.

Each sheet has a `layout`, an optional `primary_key`, and `fields`, keyed by local field name.

- `kind: cells`: requires `column_count`. Each field needs a unique uppercase A1 `location`, such as `E3`.
- `kind: table`: requires `start_row`. Field order defines columns; omit field `location` and layout `column_count`.

Table column counts are derived from the fields. An explicit `column_count` must match that count.

Rows before `start_row` are preamble.

An optional `row_count` declares the exact physical row count. Fixed locations must fit declared dimensions.

Coordinates are one-based. An empty sheet can declare `fields: {}`; an empty table derives a column count of zero.

## Primary keys

A table can declare one field or an ordered combination of fields as its primary key:

```yaml
primary_key: [station, observation_id]
```

Every component must be filled; the combination identifies one row within that sheet of a submitted form.

Individual components may repeat in a composite key. Field-level `unique` still means individual uniqueness.

Use local field names. Keys must be nonempty, contain no repeated fields, and reference declared table fields.

Omit `primary_key` when no key is defined. Fixed-cell sheets cannot declare a primary key.

`form_sheets()` and `form_definition()` retain the declaration without changing field constraints.

This package checks key definitions. Checking submitted key values belongs to the validation package.

## Optional composite keys and row completeness

Table sheets can declare these independent constraints:

```yaml
unique_key: [station, category]
at_least_one: {fields: [category, count], severity: warning}
```

`unique_key` checks uniqueness only when every component is supplied and correctly typed.
It does not make its fields mandatory. Every occurrence of a duplicate complete key is reported as an error.
A sheet may declare both a primary key and a unique key; they are checked independently.
Parent relationships continue to reference a parent's primary key.

`at_least_one` requires a supplied value in at least one selected field on each populated row.
A row is populated when any declared field has a supplied value after preparation.
Entirely empty rows are skipped. Zero and false count as supplied values.
Both constraints use the validation engine's configured missing-value policy.

Both field lists must be nonempty, contain no duplicates, and name declared fields in the same table.
`at_least_one` accepts `error`, `warning`, or `info`; omitted severity defaults to `error`.

## YAML conventions

Use one UTF-8 document. Tags, anchors, aliases, merges, directives and document markers are unsupported.

Mapping keys must be unique strings. Unknown properties are errors.

Quote versions and codes, especially leading-zero codes such as `"01"`.

Use `true` and `false` for booleans. Quote text codes such as `"yes"`, `"no"`, `"on"` and `"off"`.

Empty YAML values, `null` and `~` mean null; `""` means an empty string. Omit unused optional properties.

Identifiers start with an ASCII letter, followed by letters, digits, dots, underscores or hyphens.

Full field IDs combine the sheet and field names: `<sheet>.<field>`.

Write field properties in this order: `mandatory` when true, `location` for cells, `type`, `required_field`, then constraints.

`schema_version: "1.0.0"` selects the package contract. `form.version` independently identifies your model version.

Change `form.version` when the model meaning changes. New schema properties require a supported contract version.

## Definition checks

Load a file with `read_form_model()` to check its structure and consistency.

Use `check_form_model()` on a definition list to get diagnostics with source, property path and message.

Consistency checks run only after structure passes. Submitted-data validation belongs to `form.validation`.
