Scenarios
Fraud review
Ask several independent propositions, combine them with weights you own, keep hard facts deterministic, and pay for measured confidence only on the expensive path.
Source: examples/fraud-review.ts
The problem#
"How risky is this order?" gives one opaque number. Nobody can explain it, tune it, or test it. Composite scoring asks several independent claims instead and combines them with weights that live in code.
The signals#
Each dimension is a proposition written in full, sent with asProposition: true so nothing is reframed:
import { is, score, semantic } from "jevascript"
const WEIGHTS = { identity: 0.35, behaviour: 0.4, shipping: 0.25 } as const
export async function assess(order: Order) {
const signals = await semantic(order).batch({
identityMismatch: score("The way this customer is paying is inconsistent with who they claim to be.", {
asProposition: true,
}),
behaviourAnomaly: score("This order is unusual for this customer's history.", { asProposition: true }),
shippingRisk: score("This shipping arrangement is designed to avoid traceability.", { asProposition: true }),
resale: is("The order pattern looks like buying for resale rather than personal use.", {
falseWhen: "A large but coherent purchase, such as one team buying equipment.",
}),
rushed: is("The customer appears to be in an unusual hurry to receive the goods."),
})
// Nothing above knows how it will be used. The combination is ours.
const composite =
signals.identityMismatch * WEIGHTS.identity +
signals.behaviourAnomaly * WEIGHTS.behaviour +
signals.shippingRisk * WEIGHTS.shipping
// Deterministic facts stay deterministic. A model reads numbers as text.
const hardFlags =
(order.customer.priorChargebacks > 0 ? 1 : 0) +
(order.payment.attempts > 2 ? 1 : 0) +
(order.customer.accountAgeDays < 1 && order.total > 1000 ? 1 : 0)
return { signals, composite, hardFlags }
}The decision#
const { composite, hardFlags } = await assess(order)
const action =
hardFlags >= 2 || composite >= 70 ? "manual review"
: composite >= 45 ? "hold for 3-D Secure"
: "approve"Three hard flags come from arithmetic on fields the order already has. Asking the model whether the account is less than a day old would be slower, costlier and less accurate than accountAgeDays < 1.
The expensive path: gate on measured confidence#
For the orders that reach manual review, being wrong is expensive. There, pay for certainty: minConfidence re-asks the question and measures how much the answer moves. If it moved too much, the async fallback escalates.
let escalations = 0
const fraud = await semantic(order).score("This order is fraudulent.", {
asProposition: true,
minConfidence: 0.9,
fallback: async () => {
escalations++
return (await seniorReviewer.assess(order)).score
},
})The sampling costs three requests instead of one, which is why it is on this path and not on every order. See Uncertainty.
Why five questions, not one#
- Each signal is logged separately, so a chart of
shippingRiskover time is possible and a chart of "risk" is not. - The weights are a diff. When the fraud team wants shipping to matter more, it is a number in a pull request.
- A test can stub
identityMismatchat 90 and everything else at 10 and assert the outcome, which is impossible with one fused question.
What to test#
- A returning customer, small order, matching addresses →
approve. - A day-old account, overnight shipping abroad, four payment attempts →
manual reviewon hard flags alone, with the model stubbed neutral. - Unstable
fraudulentanswer (mock withjitter) → the fallback runs andescalations === 1.