ThinkThen

Reference

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: 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.