# Question sets

A question set is one JSON file that holds every question `annotate` asks of each record.

*form.json, the smallest useful set: three questions and nothing else*

```
{
  "version": 1,
  "questions": {
    "steps": {
      "decide": "Does the report give steps to reproduce?"
    },
    "area": {
      "choose": "Which part of the app is this?",
      "options": [
        "export",
        "login",
        "billing"
      ]
    },
    "impact": {
      "score": "How much does this block the user?",
      "levels": [
        "None.",
        "Slows them.",
        "Blocks work."
      ]
    }
  }
}
```

A set may also carry a top-level threshold and say which part of each record a question reads.

*triage.json, a band for every decide question, and on*

```
{
  "version": 1,
  "threshold": "0.2:0.8",
  "questions": {
    "urgent": {
      "decide": "Does the customer need an answer today?",
      "on": ["/subject", "/body"]
    },
    "team": {
      "choose": "Which team owns this?",
      "options": {
        "billing": "Invoices, fees, and refunds.",
        "shipping": "Parcels and delivery."
      },
      "on": "/body"
    }
  }
}
```

## The top level

- `version` is the number 1.
- `questions` holds the questions, each under its name.
- `threshold` applies to every `decide` question that names none. Here `urgent` takes the band 0.2:0.8.
- `profile` names the backend profile the thresholds were set for.
- `batch` is `"max"` or a whole number above 0. A flag or `THINKTHEN_BATCH` outranks it.

Those five keys are the only ones allowed. The set names no address, model or output format.

## Each question

A name uses lowercase letters, digits and underscores, and is not empty. The name becomes the answer's field in the output. Each question has the shape of one [question file](/functions/question-file/): one of `decide`, `choose`, `tag` or `score`, with its own options, levels and threshold. A question may not carry `batch` or its own `profile`.

## on

`on` picks the part of the record a question reads. It is one JSON Pointer or a list of them, read inside the record or inside what `--field` selected. Several pointers send one object, keyed by the last part of each pointer. Two pointers that end in the same name are an error in the file. A question with no `on` reads the whole record, or what `--field` selected.

*The plan shows each question's pointers. urgent reads both fields as one object, keyed subject and body. team reads only the body.*

```
cat <<'EOF' |
{"id": "T-1", "subject": "Refund", "body": "Please refund the fee."}
EOF
thinkthen annotate triage.json \
  --jsonl \
  --plan |
jq 'select(.on) | .on,
  (.request.questions | map_values(.instructions))'
```

*Output*

```
{
  "urgent": [
    "/subject",
    "/body"
  ],
  "team": [
    "/body"
  ]
}
{
  "q1": "The text is {\"subject\":\"Refund\",\"body\":\"Please refund the fee.\"}. Does the customer need an answer today?",
  "q2": "The text is \"Please refund the fee.\". Which team owns this?"
}
```

*exit 0*

A text record has no parts. A question with `on` refuses it at exit 2. Under `--lines`, such a set is refused before any input is read.

## What the file refuses

An unknown key anywhere in the file, a missing `questions` object, a wrong `version` and a bad name each stop the run at exit 5, before any request. The message names the key at fault.

[annotate's edge cases](/reference/annotate/) · [Question files](/functions/question-file/) · [The question-set specification](https://github.com/botassembly/thinkthen/blob/main/specification/annotate.md#the-question-set)

On GitHub: [github.com/botassembly/thinkthen](https://github.com/botassembly/thinkthen)
