Jev Case Study: Loan Application Risk Tiering (Full Code)

Updated Applies to Jev 1.13 Example fixture

TL;DR: This Jev case study shows a risk-tiering pipeline for a small lending platform: a choice question sorts each application into low, medium, or high risk, and a judgment question decides whether the application needs a human reviewer. A confidence threshold routes the flow — auto-decide at 0.8 and above, human review in the middle band, and a full manual pass below 0.5. Full request bodies, example-fixture responses, and a runnable Python script are below.

Scenario

We work with a small consumer-lending platform that processes about 9,000 applications a month. Their existing rules engine only knows hard cutoffs: FICO below 620 goes to decline, FICO above 720 and debt-to-income under 36% goes to auto-approve, everything in between lands on a human reviewer’s desk. That works fine for the numbers — and misses everything the numbers don’t say.

The gap showed up in post-mortems. Applications where the employment description contradicts itself (“five years at a company founded in 2023”), where a stated salary doesn’t match the stated industry, or where the reason for the loan shifts between fields — those sailed through if the credit score happened to clear the bar. The rules engine can’t weigh soft signals, and human reviewers can’t scale to 9,000 applications.

So we added two Jev calls per application, sitting between the rules engine and the reviewer queue:

  1. A choice question — assign the application to exactly one risk tier: low, medium, or high.
  2. A judgment question — does this application need manual review, regardless of tier? Answer yes, no, or unclear.

Jev is a System One judgment model from TypeSafe AI, released in September 2026. It fits this slot because the output is typed JSON with a confidence value, not free-form prose — which is what a routing gate needs. The example application below is deliberately mixed: a decent FICO (678) that would normally trigger a middle-tier review, plus two soft contradictions the rules engine never sees.

Request

Call 1 — risk tier (choice question):

{
  "model": "typesafe/jev-1.13",
  "messages": [
    {
      "role": "system",
      "content": "Choice question. Assign this loan application to exactly one risk tier: low, medium, or high. Consider stated income vs. industry norms, internal consistency of employment history, and coherence of the stated loan purpose. Reply with JSON only: {\"answer\": <low|medium|high>, \"confidence\": <0-1>, \"rationale\": <one sentence>}"
    },
    {
      "role": "user",
      "content": "Loan application #A-48213: FICO 678. Stated monthly income: $9,200, occupation: warehouse associate. Employment: \"shift lead at a regional logistics company for 5 years.\" Requested amount: $12,000 over 36 months. Purpose field 1: \"consolidate credit cards.\" Purpose field 2 (free text): \"help pay for my nursing school tuition starting this fall.\" DTI: 31%."
    }
  ]
}

Call 2 — manual-review check (judgment question):

{
  "model": "typesafe/jev-1.13",
  "messages": [
    {
      "role": "system",
      "content": "Judgment question. Answer yes, no, or unclear. Reply with JSON only: {\"answer\": <yes|no|unclear>, \"confidence\": <0-1>, \"rationale\": <one sentence>}"
    },
    {
      "role": "user",
      "content": "Loan application #A-48213: FICO 678, DTI 31%. Claims $9,200/month as a warehouse associate and 5 years at a logistics company. Loan purpose is stated as credit card consolidation in one field and nursing school tuition in another. Does this application need manual review?"
    }
  ]
}

Response

Both responses below are example fixtures — illustrative output, not a live capture.

Call 1:

{
  "answer": "medium",
  "confidence": 0.71,
  "rationale": "Credit metrics are acceptable, but the stated income is high for the occupation, and the loan purpose is inconsistent between fields."
}

Call 2:

{
  "answer": "yes",
  "confidence": 0.88,
  "rationale": "The mismatched loan purposes and income-to-occupation gap are exactly the soft signals the rules engine cannot evaluate."
}

Reproduce

Save as risk_tier.py, fill in your own BASE_URL and API key, and run python3 risk_tier.py. It uses the OpenAI-compatible SDK, so any OpenAI-compatible endpoint works:

import json, os
from openai import OpenAI

BASE_URL = "https://YOUR-ENDPOINT.example.com/v1"  # replace with your provider
client = OpenAI(base_url=BASE_URL, api_key=os.environ["JEV_API_KEY"])
MODEL = "typesafe/jev-1.13"  # confirm exact slug with your provider

def ask(system, user):
    res = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "system", "content": system},
                  {"role": "user", "content": user}])
    return json.loads(res.choices[0].message.content)

CHOICE = ('Choice question. Assign this loan application to exactly one risk '
          'tier: low, medium, or high. Consider stated income vs. industry '
          'norms, internal consistency of employment history, and coherence '
          'of the stated loan purpose. Reply with JSON only: '
          '{"answer": <low|medium|high>, "confidence": <0-1>, '
          '"rationale": <one sentence>}')

JUDGE = ('Judgment question. Answer yes, no, or unclear. Reply with JSON '
         'only: {"answer": <yes|no|unclear>, "confidence": <0-1>, '
         '"rationale": <one sentence>}')

app = ("Loan application #A-48213: FICO 678. Stated monthly income: $9,200, "
       "occupation: warehouse associate. Employment: 'shift lead at a "
       "regional logistics company for 5 years.' Requested amount: $12,000 "
       "over 36 months. Purpose field 1: 'consolidate credit cards.' "
       "Purpose field 2 (free text): 'help pay for my nursing school "
       "tuition starting this fall.' DTI: 31%.")

tier = ask(CHOICE, app)
review = ask(JUDGE, "Loan application:\n" + app + "\n\nDoes this application need manual review?")

def route(tier, review):
    conf = tier["confidence"]
    if conf >= 0.8:
        return "auto:" + tier["answer"]
    if conf >= 0.5:
        return "human-review"
    return "human-review-priority"

print(tier)
print(review)
print(route(tier, review))

Confidence routing

The tier alone is not the decision — confidence is. Our routing rule:

tier confidence >= 0.8  ----> auto-apply tier
tier confidence 0.5-0.8 --> human review queue
tier confidence < 0.5   ----> priority manual review
review answer = yes     ----> force human review (overrides tier)
review answer = unclear --> treat as yes

The middle band is where most applications actually land, and that is fine — the point of the gate is not to automate everything, it is to make sure only well-understood cases skip a human.

Key parameters

FieldValueWhy it matters
modeltypesafe/jev-1.13Pin the version so a newer release cannot silently change risk decisions. Confirm the exact slug with your provider.
messages[0] (system)Question type + option list + JSON shape + the soft signals to weighNaming the soft signals (income vs. industry, consistency, purpose coherence) keeps scoring stable across applications.
messages[1] (user)Application fields, verbatimSend the raw field values, not a summary — the contradictions live in the details.
answer (response)medium / yes (example fixture)The tier maps to the routing table; the review flag overrides it.
confidence (response)0.71 / 0.88 (example fixture)The main routing input: ≥0.8 auto, 0.5–0.8 review, <0.5 priority review.
rationale (response)One sentenceGoes straight into the reviewer’s case notes.

Notes

Keep reading