Worked examples

Three queries with a defect in each, the report jevlint check prints on them, and the same query rewritten until it clears. Each report is output from a run, not written for this page.

A Score whose levels are numbers

score/numeric-levels is a rule, so this costs no call and needs no key. It has no patch, because which situation each level stands for is a judgement about your rubric.

The query

{
  "state": {"ticket": "The login page is broken and nobody on my team can work."},
  "questions": {
    "severity": {
      "type": "score",
      "instructions": "Rate the severity of this ticket from 0 to 2.",
      "criteria": ["0", "1", "2"]
    }
  }
}

What jevlint reports

1-before.json · catalogue v1 51d384eb777b for jev-1.13

severity
  error    Score levels are bare numbers
           score/numeric-levels
           The levels are bare numbers, so there is nothing in the state for the model to match
           them against.
           Likelihood  certain, because this rule either matches or does not
           Found       Levels: 0, 1, 2.
           Suggested   Replace each number with the situation it stands for. For example, a
                       three-level severity rubric becomes "Cosmetic, and nobody's work is
                       affected", "Broken or degraded, but a workaround exists", "No workaround,
                       and the work has stopped".
           Why         The model never sees a level's number or its neighbours, so a bare number
                       gives it nothing to match against. The documented example scores 0.55 at
                       confidence 0.33 with numeric levels and 0.0 at confidence 1.0 with
                       described ones.
           Docs        https://docs.typesafe.ai/primitives/score#writing-good-levels

note   Jev was not asked, so the 20 checks that ask it were not run.

1 error, 0 warnings, 0 advice · no calls made

The query rewritten

{
  "state": {"ticket": "The login page is broken and nobody on my team can work."},
  "questions": {
    "severity": {
      "type": "score",
      "instructions": "How badly is this ticket blocking the customer's work?",
      "criteria": [
        "Cosmetic, and nobody's work is affected",
        "Broken or degraded, but a workaround exists",
        "No workaround, and the work has stopped"
      ]
    }
  }
}
1-after.json · catalogue v1 51d384eb777b for jev-1.13

Nothing to report.

note   Jev was not asked, so the 20 checks that ask it were not run.

0 errors, 0 warnings, 0 advice · no calls made

A Choice with nowhere to put the rest

Two findings on one question, both rules. choice/no-fallback has a patch, marked lossy because adding one key rewrites the object around the labels you wrote. question/instruction-is-id has none: only you know what the question is asking.

The query

{
  "state": {"ticket": "I was charged twice for order A-104. Please refund the duplicate."},
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which department?",
      "criteria": {"billing": "Money things", "payments": "Anything to do with money"}
    }
  }
}

What jevlint reports

2-before.json · catalogue v1 51d384eb777b for jev-1.13

department
  warning  Choice has no fallback option
           choice/no-fallback
           None of the options is an `other` or `none of the above`.
           Likelihood  certain, because this rule either matches or does not
           Found       Options: billing, payments.
           Suggested   Add `"other": "Anything the other options do not cover"`.
           Patch       lossy · add /questions/department/criteria = {"billing":"Money
                       things","payments":"Anything to do with money","other":"Anything the
                       other options do not cover"}
           Why         Without a catch-all the probability mass has nowhere to go, so it lands
                       on the nearest wrong label. Measured on decision-v7: where the answer was
                       none of the options, removing the catch-all put a mean 0.79 on a label
                       that could not be right and cost a Brier score of 0.660 against 0.349
                       with it. Where the answer was among the options it cost nothing.
           Docs        https://docs.typesafe.ai/primitives#choose-a-question-type

  warning  Instruction too short to stand on its own
           question/instruction-is-id
           The instruction is short enough that it leans on the question id for its meaning.
           Likelihood  certain, because this rule either matches or does not
           Found       Instructions: "Which department?".
           Suggested   Write the question out in full, so `instructions` says what is being
                       asked without `department` beside it.
           Why         Question ids are for your code and are never sent, so whatever the id
                       carries does not reach the model.
           Docs        https://docs.typesafe.ai/primitives#define-a-question

note   Jev was not asked, so the 20 checks that ask it were not run.

A patch says what kind of change it is: lossy, what you wrote survives, something around it changes.

0 errors, 2 warnings, 0 advice · no calls made

The query rewritten

{
  "state": {"ticket": "I was charged twice for order A-104. Please refund the duplicate."},
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which department should handle this ticket?",
      "criteria": {
        "billing": "Invoices, charges and refunds",
        "shipping": "Delivery, tracking and returns",
        "other": "Anything the other options do not cover"
      }
    }
  }
}
2-after.json · catalogue v1 51d384eb777b for jev-1.13

Nothing to report.

note   Jev was not asked, so the 20 checks that ask it were not run.

0 errors, 0 warnings, 0 advice · no calls made

One question asking two things

Nothing about the shape of this query is wrong, so finding the defect costs calls. Splitting one question into two then doubles what checking it costs: 6 calls against 3.

The query

{
  "state": {"ticket": "Hi, I am a long-time customer and my invoice looks wrong this month."},
  "questions": {
    "not_spam": {
      "type": "noul",
      "instructions": "Is this ticket free of spam and also written by a real customer?"
    }
  }
}

What jevlint reports

3-before.json · catalogue v1 51d384eb777b for jev-1.13
A probability is how strongly the defect is present, so higher is worse.

not_spam
  error    The question weighs several factors at once
           question/compound-judgment
           Answering this weighs several separate factors to reach one answer.
           Likelihood  0.91, almost certain, against a 0.70 trigger
           Found       Read: "Is this ticket free of spam and also written by a real customer?"
           Suggested   Ask one question per factor and weight them in code, so you can see which
                       factor moved the answer. For example, a question about whether to
                       interview someone becomes one about experience, one about the skills the
                       role needs, and one about availability.
           Why         Ask one question per factor and combine them in code, where you can see
                       and change the weights. One answer carrying several judgements means
                       less, and you cannot tell which factor moved it. Measured on
                       decision-v7's policy cases: joining a second condition that every case
                       already satisfies, so the right answer does not change, still took the
                       answers from 15 of 20 to 11 of 20 and more than doubled the Brier score,
                       and splitting it recovered all of it. That is the largest cost measured
                       for any check here. Accept it where the factors always move together, so
                       no material can satisfy one and not the others, or where the caller wants
                       the blended judgement and will never read the parts: a severity rubric
                       that composites how bad and how urgent is the documented shape, not a
                       defect.
           Docs        https://docs.typesafe.ai/primitives#ask-for-one-snap-judgment-per-question

  warning  A yes answer means the thing is absent
           noul/negated-phrasing
           A yes answer here means the thing is absent, which reads backwards in the code that
           uses it.
           Likelihood  0.91, almost certain, against a 0.70 trigger
           Found       Read: "Is this ticket free of spam and also written by a real customer?"
           Suggested   Ask about the thing being present rather than absent: "Is the message
                       free of personal data?" becomes "Does the message contain personal
                       data?". This flips which answer means what, so the code reading it has to
                       flip in the same change - a question reworded on its own and a caller
                       left as it was inverts every result and reports nothing.
           Why         A high probability then means the thing is missing, and the code that
                       reads it later gets the sense backwards. The harm this names is in the
                       code that reads the answer, not in the answer, so measuring answer
                       quality cannot see it and none is reported here. Accept it where the code
                       reading the answer is written for this polarity and says so; the risk
                       here is a later reader changing one side of that pair without the other.
           Docs        https://docs.typesafe.ai/primitives/noul#writing-a-noul-question

  advice   Noul does not say what yes and no mean
           noul/no-criteria
           The question carries no criteria, so where the line between yes and no falls is left
           to the model.
           Likelihood  certain, because this rule either matches or does not
           Suggested   Add a `criteria` object with a `true` and a `false` description, saying
                       which material counts as each.
           Why         The instruction is enough for many Nouls. Where the boundary is subtle,
                       saying what a yes and a no mean is what moves a question from guessing to
                       deciding.
           Docs        https://docs.typesafe.ai/primitives/noul#writing-a-noul-question

note   The checks that compare questions need two to compare. This run carried 1, so they did not run.

1 error, 1 warning, 1 advice · 3 calls, 3,119 tokens

The query rewritten

{
  "state": {"ticket": "Hi, I am a long-time customer and my invoice looks wrong this month."},
  "questions": {
    "is_spam": {
      "type": "noul",
      "instructions": "Is this ticket spam?",
      "criteria": {
        "true": "Unsolicited advertising, a mass mailing, or a message with no support request in it.",
        "false": "A message asking for help with the product or an account."
      }
    },
    "from_a_real_customer": {
      "type": "noul",
      "instructions": "Does the sender write as somebody who already uses the product?",
      "criteria": {
        "true": "The message refers to their own account, order, invoice or usage.",
        "false": "The message shows no sign the sender has an account."
      }
    }
  }
}
3-after.json · catalogue v1 51d384eb777b for jev-1.13

Nothing to report.

0 errors, 0 warnings, 0 advice · 6 calls, 6,851 tokens