sanctionsSanctions and watch-list screening
Compares a customer or payment party with one sanctions or watch-list entry - match, possible match (check it) or no match.
Data: Real OpenSanctions entries (CC BY-NC 4.0) with customer records built by code; code fixes every label, and GLM 5.3 writes name variants and notes.
Licence, in plain words
The adapter: To be decided (trained on data licensed for non-commercial use).
- OpenSanctions default collection (export 20261002125305-bic): non-commercial use only (Creative Commons Attribution-NonCommercial 4.0 (CC BY-NC 4.0); commercial use needs a separate licence from OpenSanctions)
Trained on Jeff v1.3. Adapters are tied to the exact base they were trained on. What changed in v1.3
Use it when
- You screen new customers or payment parties against watch-list entries and want a fast first decision for each pair.
- You want "possible match" kept apart from "match", so a person checks only the pairs where the deciding detail is missing.
- Your rule is the one the adapter was trained on - date of birth decides for people, country of registration for companies, IMO number for ships.
Not a good fit when
- You need to search a whole list. The adapter compares one customer with one entry; find candidate entries first (for example by name search), then ask it about each pair.
- Your matching rule differs (for example nationality or address must decide). It was trained on one rule, stated in the instructions.
- You need a final decision without a person. Screening decisions have legal consequences; use the adapter to sort, not to clear on its own.
- You plan commercial use. The training data is licensed for non-commercial use only (see Data and licence).
Request format
The state is an object with these fields, in this order. Only customer_record changes from request to request, so it comes last and the rest can be prepared in advance.
| State field | Changes per request | What goes in it |
|---|---|---|
watch_list_entry | No | One entry from the list: name, aliases, type (person, company or organisation, ship), dates of birth, countries, IMO number and the lists it is on. |
customer_record | Yes | The customer or payment party as your records show it, as fields or as a short note. |
| Question | Type | What it decides |
|---|---|---|
screening | Choice | Whether the customer is the listed party. Options: |
- Use the instructions below word for word; every training row used them.
- Keep the entry the same across the customers you compare with it, and put the customer record last, so the entry can be prepared in advance.
General rules for every request are in the request format guide.
Example
The same request three ways. It assumes a Jeff server on your machine with this adapter loaded (see Install).
from jeff import Client
from jeff.client import choice_question
jeff = Client("http://localhost:8765", model="sanctions")
state = {
"watch_list_entry": "Name: HH WOODCHIP\nAliases: PRIMROSE 6969\nType: ship\nCountries: Belize, Panama, Tanzania\nIMO number: 9118410\nLists: Tokyo MoU Detention List",
"customer_record": "The ship Hh Woodchip, a dredger with IMO 9381469, is flagged in Belize and appears under reference 87965. The payment is in yuan with a value date of February 10, 2010.",
}
answers = jeff.ask(state, {
"screening": choice_question(
{
"match": "Match: the customer is the listed party. The names agree and the deciding detail agrees with the entry.",
"possible_match": "Possible match: the names agree and nothing contradicts the entry, but the deciding detail is missing from the customer record or from the entry, so a person must check it.",
"no_match": "No match: the customer is a different party. The names clearly differ, the customer is a different kind of party, or the deciding detail contradicts the entry.",
},
"A bank checks each customer, or each party named in a payment, against one entry from a sanctions list or another watch list. Is the customer the party on the list? Names agree when the customer's name is the listed name or one of its aliases, allowing for other spellings or transliterations, a different order of the names, initials, a missing middle name and small typing mistakes. The deciding detail is the date of birth for a person, the country of registration for a company or organisation, and the IMO number for a ship. A year of birth alone cannot confirm a person, but a different year rules them out. A different kind of party, such as a ship instead of a person, is never the listed party. Nationality, occupation and the other details do not decide.",
),
})
print("screening", answers.choice("screening").key)import { Client, choiceQuestion } from '@jeff/client';
const jeff = new Client({ url: 'http://localhost:8765', model: 'sanctions' });
const state = {
watch_list_entry: 'Name: HH WOODCHIP\nAliases: PRIMROSE 6969\nType: ship\nCountries: Belize, Panama, Tanzania\nIMO number: 9118410\nLists: Tokyo MoU Detention List',
customer_record: 'The ship Hh Woodchip, a dredger with IMO 9381469, is flagged in Belize and appears under reference 87965. The payment is in yuan with a value date of February 10, 2010.',
};
const answers = await jeff.ask(state, {
screening: choiceQuestion(
{
match: 'Match: the customer is the listed party. The names agree and the deciding detail agrees with the entry.',
possible_match: 'Possible match: the names agree and nothing contradicts the entry, but the deciding detail is missing from the customer record or from the entry, so a person must check it.',
no_match: 'No match: the customer is a different party. The names clearly differ, the customer is a different kind of party, or the deciding detail contradicts the entry.',
},
'A bank checks each customer, or each party named in a payment, against one entry from a sanctions list or another watch list. Is the customer the party on the list? Names agree when the customer\'s name is the listed name or one of its aliases, allowing for other spellings or transliterations, a different order of the names, initials, a missing middle name and small typing mistakes. The deciding detail is the date of birth for a person, the country of registration for a company or organisation, and the IMO number for a ship. A year of birth alone cannot confirm a person, but a different year rules them out. A different kind of party, such as a ship instead of a person, is never the listed party. Nationality, occupation and the other details do not decide.',
),
});
console.log('screening', answers.screening.key);curl -s http://localhost:8765/v1/systemone \
-H 'content-type: application/json' \
-d '{
"model": "sanctions",
"state": {
"watch_list_entry": "Name: HH WOODCHIP\nAliases: PRIMROSE 6969\nType: ship\nCountries: Belize, Panama, Tanzania\nIMO number: 9118410\nLists: Tokyo MoU Detention List",
"customer_record": "The ship Hh Woodchip, a dredger with IMO 9381469, is flagged in Belize and appears under reference 87965. The payment is in yuan with a value date of February 10, 2010."
},
"questions": {
"screening": {
"type": "choice",
"instructions": "A bank checks each customer, or each party named in a payment, against one entry from a sanctions list or another watch list. Is the customer the party on the list? Names agree when the customer'\''s name is the listed name or one of its aliases, allowing for other spellings or transliterations, a different order of the names, initials, a missing middle name and small typing mistakes. The deciding detail is the date of birth for a person, the country of registration for a company or organisation, and the IMO number for a ship. A year of birth alone cannot confirm a person, but a different year rules them out. A different kind of party, such as a ship instead of a person, is never the listed party. Nationality, occupation and the other details do not decide.",
"criteria": {
"match": "Match: the customer is the listed party. The names agree and the deciding detail agrees with the entry.",
"possible_match": "Possible match: the names agree and nothing contradicts the entry, but the deciding detail is missing from the customer record or from the entry, so a person must check it.",
"no_match": "No match: the customer is a different party. The names clearly differ, the customer is a different kind of party, or the deciding detail contradicts the entry."
}
}
}
}'A real response from this adapter appears here when it is released.
Results
On this adapter's held-out test set, never trained on, scored three ways on the same rows: the untrained model Jeff is built from, the Jeff v1.3 base alone, and the base with this adapter. Every question has 3 options. As of 2026-10-05. All adapters
| Test set | Test rows | Qwen3.5-0.8B untrained | Jeff base v1.3 alone | Jeff base v1.3 + adapter |
|---|---|---|---|---|
test | 4,909 | 34.4% · 0.010 | 68.3% · 0.080 | 100.0% · 0.001 |
test4,909 test rows- Qwen3.5-0.8B untrained
- 34.4% · 0.010
- Jeff base v1.3 alone
- 68.3% · 0.080
- Jeff base v1.3 + adapter
- 100.0% · 0.001
Each cell: accuracy · calibration error (ECE; lower is better, 0 is perfect).
With llama.cpp
The same test, through llama.cpp: the base GGUF plus this adapter's LoRA GGUF, with the temperature refitted for each format. Running Jeff with llama.cpp
| Test set | Full precision | Q8_0 | Q4_K_M |
|---|---|---|---|
test | 100.0% · 0.001 | 100.0% · 0.001 | 99.9% · 0.001 |
Source: results/sources/v1.3/new-adapters.table.json
Model card
sanctions: how it was made
How it was made
In short: Real OpenSanctions entries (CC BY-NC 4.0) with customer records built by code; code fixes every label, and GLM 5.3 writes name variants and notes.
- Test set: Split by listed entity. About 9.5% of the listed persons, companies and ships (825 entries) are only in the test set; no entry, and no real entity used as a different-party customer, appears in both training and test.
- Training data: not published.
- Training mixed in a replay sample of the Jeff base model's own training data: 4,466 rows, about 10% on top of the adapter's 44,663 (a precaution; its effect has not been measured).
- The test may be too easy: the adapter scores 100.0% on it, and the blind check also agreed on 200 of 200 rows. We are checking whether the answer can be read off a few fields. Treat the number as an upper bound until that check is done.
- Real homonyms (the same name, a different person) are rare in the source data (76 rows in all); most different-party rows differ in name or detail more clearly.
- Training mixes in about 10% of the base model's own training data.
| What it did | Model | Where it ran | Rows |
|---|---|---|---|
Wrote the customer's name variant (spelling, transliteration, order, initials, typo, format), checked by code name_source | GLM 5.3 | local | 21,940 |
Rewrote the customer record as an onboarding note with the same facts, checked by code glm_note.model | GLM 5.3 | local | 14,508 |
Data and licence
Adapter: To be decided (trained on data licensed for non-commercial use)
- OpenSanctions default collection (export 20261002125305-bic)Creative Commons Attribution-NonCommercial 4.0 (CC BY-NC 4.0); commercial use needs a separate licence from OpenSanctionsNot generated by a modelAggregates many official lists (OFAC, EU, UN, UK, INTERPOL, national exclusion and debarment lists and others), each with its own terms. Citation - OpenSanctions (2026), OpenSanctions Default dataset, version 20261002125305-bic.
- Customer records, name variants and onboarding notesBuilt for this adapter; terms follow the adapter's licenceGenerated by GLM 5.3 (own hardware), for name variants and notesCustomer records built by code from the entries and from other real entities in the collection. Name variants and notes were written by GLM 5.3 and checked by code.
Check it yourself
Changelog
- 1.3.0 · 2026-10-03First release, trained on Jeff v1.3 with the live-last prompt layout (LoRA rank 16, one epoch, about 10% of the base model's own training data mixed in). Not yet on Hugging Face.
Comments
Comments open when JeffHub launches. They will live in the registry repository's GitHub Discussions, one thread per adapter; you sign in with GitHub, and JeffHub stores no accounts.
