---
title: "How We Use Jev (TypeSafe) to Solve Fuzzy Problems in…"
description: "Replace fragile keyword matching with typed AI judgments: how BudgetWorks OS uses Jev for inbox triage, receipt categories and call handling, with real…"
url: https://www.budgetapp.works/en/blog/how-we-use-jev-select-dont-generate
canonical_url: https://www.budgetapp.works/en/blog/how-we-use-jev-select-dont-generate
last_updated: 2026-10-12
---

[ Back to Blog](/en/blog)

 October 1, 2026 56 views

# Select, Don't Generate: How We Use Jev to Solve Fuzzy Problems

![Select, Don't Generate: How We Use Jev to Solve Fuzzy Problems](/cdn-cgi/image/width=1536,quality=80,format=auto/https://public-api.budgetapp.works/api/public/media/1b6623cb-6fc5-41fe-8ccb-e6df054e6f82)

## Select, Don't Generate: How We Use Jev to Solve Fuzzy Problems in BudgetWorks OS

Every business system has a few places where plain code is not enough. Which project does this scanned invoice belong to? Is this receipt a meal or a supply purchase? Did the caller just say "quite interested" or "quit"?

For years we solved these with keyword lists and `ILIKE '%word%'`. They worked until they didn't, and when they failed they failed silently. This post is about how we now use **Jev**, a System One model from TypeSafe, to fix that, and the one rule that keeps it safe.

## What Jev is (and is not)

Jev does not write text. It answers **typed questions** and returns a probability with each answer:

| Primitive | Returns                                                     | Use it for                                                |
| --------- | ----------------------------------------------------------- | --------------------------------------------------------- |
| choice    | One option from a list, plus a probability for every option | Classification, routing, "which one is it?"               |
| noul      | Probability of yes / no                                     | Condition checks: "is the caller asking to end the call?" |
| score     | A weighted position on 2–10 ordered levels                  | Urgency, risk, relevance                                  |

That is the whole idea: **select instead of generate**. A chat model asked "which project is this?" invents an answer in free text that we then have to parse and validate. Jev can only pick from the options we hand it, and tells us how sure it is.

## The rule: code decides what is possible, Jev decides what is meant

```mermaid
flowchart LR
    A[Document or message arrives] --> B[Code loads the real candidates from the database]
    B --> C[Jev choice: which one, or none?]
    C --> D{Confident?}
    D -->|Yes| E[Apply automatically]
    D -->|No| F[Show top 3 candidates to a human]
    E --> G[Audit trail]
    F --> G

```

Rules, arithmetic and exact lookups stay in code. Only the step that needs _understanding_ goes to Jev. The options always come from our own database (this tenant's active projects, chart of accounts, vendors), so Jev can never answer with something that does not exist. And low confidence goes to a person, which matches the preview-then-confirm design we already use for AI agents.

## Problems we found with the old approach

When we audited the backend for fragile logic, the failures were not exotic:

* **Phone calls ended by accident.** The AI call engine hung up when the message _contained_ "quit" or "stop" as a substring. "I'm **quite** interested" ends the call. "Thanks, that's all" does not. It ran on every turn of every call.
* **Inbox auto-triage guessed.** Documents were matched to a project with `project_name ILIKE '%keyword%'`, first hit wins. A keyword like "ltd" or "hk" matches almost any project, and the result was an automatically created task and a moved file, with no confidence and no confirmation.
* **Receipt categories by substring.** "bar" matched _barber_, "tea" matched _team_, "gas" matched _Vegas_. The category list was hard-coded and Chinese receipts mostly landed in `other`.

## What we did, in order

1. **Fixed the obvious bugs first, without Jev.** The call engine now matches whole words, and inbox triage no longer accepts short generic keywords or moves files without confirmation. A bug that regex can fix does not need a model.
2. **Wrote one small client.** A single service wraps the TypeSafe API. It never raises: if the key is missing, the request times out or the service is overloaded, it returns nothing and the existing logic carries on. The API key stays on the server.
3. **Ran Jev in shadow mode.** Jev answers in the background and we log its answer next to what the existing logic decided. Nothing changes for users. We compare on real data before trusting it.

## Early results from shadow mode

We backfilled 30 real receipts and compared Jev's category with the vision model's category:

| Measure                              | Result         |
| ------------------------------------ | -------------- |
| Agreement overall                    | 24 of 30 (80%) |
| Agreement when Jev confidence ≥ 0.90 | 20 of 23       |
| Median latency                       | about 412 ms   |

The disagreements were the interesting part. A convenience-store purchase of cigarettes was labelled `meals` by the old logic; Jev said `other`, and Jev was right. A forklift rental was `other` vs `supplies`, which is a genuine judgement call. Three receipts where the vision model returned nothing at all still got a sensible answer from Jev.

This is a small sample, and we say so. It is enough to tell us the approach is worth continuing, not enough to switch it on.

## Where we use it next

| Problem                        | Jev question                                                                                                       |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| Inbox document → project       | choice over the tenant's active projects plus "none"                                                               |
| Receipt category               | choice over the tenant's expense accounts                                                                          |
| Ending or transferring a call  | Two noul questions on the last few turns, asked in one request                                                     |
| Spreadsheet import mapping     | choice per column over the real target columns plus "skip"                                                         |
| Invoice payment reconciliation | Code filters candidate invoices by amount and date, choice picks the match, noul asks "is this a partial payment?" |

## What stays out of Jev

Just as important is what we did _not_ hand over: SKU rules that users define themselves, date and total extraction from receipts, assessment scoring formulas, data-type inference. These are deterministic, so they stay deterministic. Anything that generates text (reports, proposals, chat) stays with a language model, and anything that reads images stays with a vision model. Jev only reads text.

## Takeaways

* Use a typed answer with a probability instead of free text you have to parse.
* Give the model options from your own data, plus a "none of these".
* Fix what code can fix first. Add the model only for the step that needs meaning.
* Run it in shadow mode and compare with real data before it changes any behaviour.
* Use the probability as a threshold: confident means automatic, unsure means a person decides.

_Want to see how this works for your own documents and workflows? [Get in touch](/contact)._

 #jev #typesafe #ai #classification #shadow-mode #automation

### Want to see it in action?

Learn how BudgetWorks OS can transform your business operations.

[Try Live Demo](/en/try)

---
Site map for agents: [llms.txt](https://www.budgetapp.works/llms.txt) · [sitemap.md](https://www.budgetapp.works/sitemap.md)
