---
title: Small Open Models for Insurance
description: How to break a claims job into steps, give small open models the invoices and calls to read and code the checks against the claim, and hand an SIU review of one repair shop to an agent the workflow keeps honest.
canonical_url: https://superlinked.com/blog/insurance-claims-on-small-open-models
last_updated: 2026-11-11
---

An [insurer](/insurance) runs on people reading things and deciding what to do about them. Underwriters read submissions, loss runs and inspection reports to decide whether to take on a risk and at what price. Claims teams take the first notice of loss, usually a call or a web form, and then live with the paper that follows a claim for weeks: estimates, invoices, photos, recorded statements and the policy wording the loss turns on. The special investigations unit looks across claims for the fraud no single claim shows, subrogation teams go back through paid claims for someone else who should cover the cost, and service teams handle the steady stream of changes to policies already in force. AI shows up in all of these, and most of what it does there is reading. In this post I'll stay with claims, where someone reads each piece of paper, compares it with the claim in the claims system, and decides what happens next (pay it, question it, call the customer back, or work out whether the policy covers it at all).

In this post I'll start with why that reading is hard to keep up with and what insurers and their vendors already do with open models. Then I'll go through how I think about a claims job: break it into steps, decide which steps run as a fixed workflow and which need an agent, and work out which steps code, a small model, a bigger model or the claims team should do. After that come three worked examples from the [SIE cookbook](https://github.com/superlinked/cookbook/tree/main/insurance): a repair invoice (with an agent on top for the special investigations unit), a recorded call and a coverage brief. I'll finish with how to look at your own claims queue the same way and how to run the recipes yourself.

## Why Claims Paper Is Hard to Keep Up With

The volume swings with the weather. A storm brings a surge of claims at once, and the paper behind every one of them; after a big storm, an insurer's claims line takes hundreds of first-notice-of-loss calls a day. Small misreads cost real money. An invoice whose subtotal doesn't match its lines gets paid at the printed total, one billed to a different customer of the same shop gets paid on the wrong claim, and a house number typed as 78 instead of 87 sends the field adjuster to the neighbor's door.

Today a claims processor checks an invoice with the invoice on one screen and the claim on the other, and call review plays recordings next to the saved claim. Nobody can listen faster than real time, so only a sample of calls gets checked. And most invoices are fine, which is its own problem: after the fortieth clean one, the forty-first gets the same quick look. On top of that, regulators want an insurer to be able to explain how it reached its decisions, including the parts it bought from a vendor (the [insurance page](/insurance) has the rules).

## What Insurers Already Do With Open Models

Insurers and the teams that build AI for them have moved a lot of their reading onto open models, and they've said so publicly:

- **First American**, the title insurer, fine-tuned Llama 3.1 8B to pull transaction and entity data from property records. Meta's [case study](https://www.llama.com/resources/case-studies/first-american/) says it processes more than one million documents a day with an average F1 of 90% on critical data fields.
- **Marsh McLennan**, the broker, uses a fine-tuned Llama-3.1-8B-instruct for intent recognition and tool selection inside LenAI, its staff assistant, according to [Predibase](https://predibase.com/blog/how-marsh-mclennan-saved-over-1-million-hours-of-team-time-with-agentic-ai). LenAI itself runs on privately hosted OpenAI models, so the small model routes and the large one does the rest.
- **FurtherAI** [tested GLM-5.3](https://www.furtherai.com/engineering-blogs/what-we-found-testing-glm-5-3) on two production insurance use cases, where Claude Opus 5.5 handles complex claims and census extraction and GLM-5.3 reviews the extracted output for completeness.
- **Bevaya** (formerly Roots Automation) [built GutenOCR](https://www.bevaya.ai/blog/gutenocr-our-approach-grounded-ocr-production-workflows) by fine-tuning Qwen2.5-VL 3B and 7B into an OCR model that answers "where is X?" on a page, so every value points back at where it was read.

First American, Marsh McLennan and Bevaya each gave one job (pulling fields from property records, picking the intent and the tool, reading a page) to a small model sized for it, often with a large model nearby for the work that needs one. FurtherAI's is the other way round: GLM-5.3 is a large open model, reviewing what another large model extracted. Three of the four also trained their model; the recipes below use models as they ship, and I've written up [when that's enough and when to fine-tune](/blog/small-open-models-across-industries#off-the-shelf-or-fine-tuned) in one place.

<BlogSieCta />

## How I Break a Claims Job Into Steps

I start by writing down the steps a claims processor takes, in order. Then I decide how those steps run. If I can write them down before the first invoice arrives and they're the same every time, it's a workflow: Python runs the steps and calls a small model where one needs reading. If the steps depend on what turns up along the way, that part goes to an agent, and I look for a way to check what the agent comes back with. I've written about [when to use a workflow, an agent or a mix of the two](/blog/agentic-vs-workflows-small-models) separately, so here I'll just show what that looks like on claims.

Then, step by step, I ask who should do it. Anything the claims system already knows (the claim number, the insured, the date and place of loss, the vendor, the authorized amount, the policy in force) is a lookup in code, and I wouldn't ask a model to guess the right policy version when the claims system has it. A job that only needs reading, like turning an invoice image into text, transcribing a call or pointing at the total on a page, goes to a small model trained for that one task. Explaining in words how a policy clause bears on a particular loss goes to a bigger 27B model. And paying, deciding coverage and setting reserves stay with the claims team.

The idea I'd most want you to take away is about the checks. A check only catches a model's mistake when it compares the model's output with something the model didn't write, like the claim in the claims system. Arithmetic over numbers the model extracted can catch a misread digit, but it can't catch a number the model made up to fit: if the model invents a number that makes the sum work, the sum works. So for every check you add, ask what it compares against, and if the answer is "another number the model gave me", it won't catch that kind of mistake. The first example shows exactly that, and it also shows that a check against the claim only helps when the made-up number pushes something past what the claim allows.

## Three Claims Examples, Worked Through

All three recipes run on synthetic data. Each case was written to test one rule, so when a recipe gets them right, that tells you the pieces are wired together correctly and nothing about how accurate it is on your claims. I wouldn't copy any of them as a product; they're there to show how I break a claims job into steps.

### Checking a Repair Invoice Against the Claim

As a first example, take a repair invoice. By the time one turns up, the claim has been open for days or weeks and the approved estimate sits in the claims system. Then the shop sends its invoice (a PDF to the claims inbox, a scan from a contractor who still prints everything, a phone photo from the insured), and it lands on the claim as an image. A processor then checks that it's this claim's invoice, from the shop on the claim, that the lines add up, that the date falls after the loss and that the total fits inside what was authorized.

Those steps never change, so the recipe, [check_invoice_against_claim](https://github.com/superlinked/cookbook/tree/main/insurance/check_invoice_against_claim), runs them as a nightly workflow over the invoices waiting on open claims.

![The invoice check: OCR reads the page; GLiNER2 finds the fields, then the claim match, the vendor, the math, the date and the authorized limit are checked; any failure, or too little text, goes to the exceptions queue with its reasons and a full pass to the payment queue; the claim handler decides on both](/blog/insurance-claims-on-small-open-models/invoice-flow.webp)

Two small models do the reading. [`lightonai/LightOnOCR-3-4B`](/models/lightonai-lightonocr-3-4b) turns the page into text, and I picked it because it returns the line items as a table, which keeps each amount in its column. Then [`fastino/gliner2-large-v1`](/models/fastino-gliner2-large-v1) points at the fields, and Python only accepts a value that's in the OCR text on the same line as its label. Everything after that is Python against the claims system: does the claim number, bill-to or service address match the claim, is the vendor one on the claim, is the date after the loss, and is the total within the estimate plus approved supplements. The check that the invoice belongs to the claim goes first, because every later check assumes you're looking at the right claim; one invoice in the set is from the right shop for exactly the estimated amount, but it's billed to someone else, and it stops there. Nothing is paid automatically. Every invoice lands in a queue for a person, with the reasons written next to it.

The invoice worth remembering prints "Sales tax (6%)" with nothing next to it. A blank tax line isn't zero, so the rules say to send it for review, but LightOnOCR-3-4B wrote "Sales tax (6%): $76.20", exactly 6% of the subtotal. The OCR model did the shop's arithmetic for it, very helpfully, and the arithmetic check then agreed with the number it had just been handed. The checks against the claim passed too, because $1,346.20 is still inside the $1,400 authorized, so nothing in the workflow could tell.

![Invoice clm-08: the page prints Sales tax (6%) with no amount; LightOnOCR-3-4B wrote $76.20 and every check passed, while LightOnOCR-2-1B left the tax blank and the invoice went to review](/blog/insurance-claims-on-small-open-models/blank-tax-line.webp)

With [`lightonai/LightOnOCR-2-1B`](/models/lightonai-lightonocr-2-1b) swapped in (one line in `rules.yaml`), the same recipe left the blank blank and the invoice went to review. One invoice doesn't make the 1B the better model, but it does tell you the OCR model is part of what you test.

**Pro-tip:** put blank fields in your OCR test set on purpose. A model that never invents a value is worth more here than one that reads slightly more of the page.

### When the SIU Asks About One Shop

The same folder has a job the workflow was never built for. A few times a month someone refers a repair shop to the special investigations unit (SIU): a desk adjuster who thinks they saw the same invoice last week, a claims manager whose loss costs at one shop look high, or a tip. An SIU analyst then pulls every claim that shop worked that year, opens each invoice next to its claim, retypes it all into a spreadsheet and then reads across the rows for patterns. For a shop with forty claims that's two or three days, and most of it is retyping what the invoice check already read when each invoice arrived.

Nobody can write down the steps of that review. Which patterns matter depends on what turns up (an invoice number seen twice, a bill-to name that belongs to another claim, the same total on two jobs), and the next referral will ask something else. So an agent decides which claims to open and what to compare, and it runs each invoice through the workflow's own function, so it gets exactly the answer the nightly run would. Then code checks the agent's report against what the workflow actually returned for each invoice, not against what the agent says it returned. It looks up every claim the shop invoiced that year and makes sure the agent ran each open one. It builds the claim-by-claim table from the workflow's own results, so the agent can't change it. It makes sure every quote really is something the workflow read on that claim. And it only lets the agent accuse the shop of something (a finding) when a check failed on at least one of the claims it names. Anything else has to be a question for the adjuster. A report that breaks those rules goes back to the agent once.

On a synthetic year of claims for one roofing shop (seven claims on the list), run five times, the agent found the same invoice billed on two claims every time and called it a finding. Two neighbors on the same street with the same flat-rate gutter job and the same total came back as a question every time, with the flat rate and the shared storm named as the likely explanation. The workflow only checks open claims, and when it's asked about a closed one, it says so in the same place a failed check would go. Once, the agent took that for a failed check and wrote it up as a finding against the shop; code sent it back, and the second report made it a question. It also kept asking about an address that turned out to be the shop's own letterhead. That's harmless, but code can't tell a useful question from a pointless one, so the analyst spends a minute on it.

The code can't tell whether a pattern is real or whether the agent's explanation is right, so the analyst still reads every finding and question. It also can't find a claim the shop billed through another vendor's claim, since the list comes from the vendor named on each claim. And a quote only proves what the workflow read, so a tax amount invented by the OCR model would pass straight through. On those seven claims the agent cost about $0.09 a job in Claude tokens (a bit over a cent per claim) on top of the workflow's own calls. The retyping goes away because the claim-by-claim table comes from results the workflow already stored when each invoice arrived; what the agent adds is deciding what to compare, which is the part that changes with every referral. The recipe also gives the agent at most 12 tool calls a job, which covers seven invoices, so a shop with forty claims would need a higher limit than the recipe ships with.

### Checking Every Recorded Call Against Its Saved Claim

For a second example, consider a claim reported by phone. An intake agent saves the claim while the caller is still talking. Later, call review checks a sample of calls by listening to each recording next to the saved claim. The recipe [check_call_against_claim_record](https://github.com/superlinked/cookbook/tree/main/insurance/check_call_against_claim_record) runs the same job on every call as a workflow, because the comparison is the same each time.

![The call check: no single saved claim, or a failed model call, sends the case to needs_review; parakeet-tdt-0.6b-v3 transcribes the call, gliner2-large-v1 pulls date, location, cause, injuries and policy, Python flags injuries and urgent help, checks each field was said and compares it with the saved claim (in_claim, fill_from_call, differs, or missing with a callback question), and the reviewer fixes the claim or books a callback, flagged claims first](/blog/insurance-claims-on-small-open-models/call-check-flow.webp)

[`nvidia/parakeet-tdt-0.6b-v3`](/models/nvidia-parakeet-tdt-0-6b-v3) transcribes each call with timestamps, `gliner2-large-v1` pulls the date, location, cause, injuries and policy number out of the transcript, and Python compares each one with the saved claim. I didn't use a chat model for that comparison, because claims operations decides what counts as missing or urgent, and they had already written it down. In one test call the caller doesn't have a policy number. From the transcript alone you'd put her on the list to call back, but the intake agent had already looked the policy up by her name, so the recipe leaves her off, and nobody phones a customer with a flooded basement to ask for paperwork that's in the flooded basement.

The weak point is the transcription. The test calls use synthetic voices that Parakeet hears well. With other voices it misheard the same values, so they no longer matched the saved claim and went to a reviewer as differences (`differs`). No later check can fix a word the transcriber got wrong.

### Drafting a Coverage Brief From the Policy Wording

The third example is a coverage brief. A desk adjuster picking up a home claim has to work out which parts of the policy the loss turns on before deciding anything. The recipe [create_coverage_brief_for_claim](https://github.com/superlinked/cookbook/tree/main/insurance/create_coverage_brief_for_claim) gives them a first look: the clauses that matter, how each seems to bear on the loss, with exact quotes, and the facts nobody has established yet. It never says covered or denied.

![The coverage brief: the claims and policy systems give the policy in force on the loss date, Qwen3-Reranker-4B scores every clause against the insured's description, Qwen3.8-27B-FP8 reads the shortlist with quotes, Python checks every quote word for word, and the adjuster investigates and decides coverage](/blog/insurance-claims-on-small-open-models/coverage-flow.webp)

The policy in force on the loss date is a lookup. [`Qwen/Qwen3-Reranker-4B`](/models/qwen-qwen3-reranker-4b) scores every clause against the insured's own description, which matters because insureds don't use policy words (one wrote "dripping for what looks like many months", which never says "seepage"). This is the one step where I use a bigger model: [`Qwen/Qwen3.8-27B-FP8`](/models/qwen-qwen3-8-27b-fp8) reads the shortlist and explains how each clause applies to the story the insured told, and Python checks every quote word for word against the policy and the claim.

That check catches an invented quote, and it only checks the words. On a backed-up drain claim, the model said the sewer backup exclusion doesn't apply, because a water backup endorsement on this policy replaces it, and every quote was real. The quote check can't say whether that reasoning holds, though; it only proves the words are in the policy. Deciding that is the adjuster's job. The model is also, left to itself, an enthusiastic question asker: a burst dishwasher hose came back with eight questions for the insured.

## Where Your Own Claims Queue Fits

To find the same kind of job in your own queue, look for a step where someone reads something that arrived from outside and compares it with a claim you already hold. The cookbook READMEs name a few: a rental car bill against approved days, a tow and storage bill, a recorded statement against the adjuster's notes, jewelry against appraisals. Write down the steps, give code everything the claims system already knows, give a small model only the pages and recordings to read, and check what it read against the claim. Where the steps can't be written down, like the SIU review of one shop, let an agent decide what to look at and have your workflow check what it finds.

## Run It Yourself

The easiest way to get a feel for this is to run a recipe on [SIE Serverless](/serverless) with the synthetic claims that ship with it. You need [uv](https://docs.astral.sh/uv/) and an [SIE API key](https://console.superlinked.com/console/keys):

```bash
git clone https://github.com/superlinked/cookbook
cd cookbook
cp .env.example .env    # then set SIE_API_KEY
cd insurance/check_invoice_against_claim
uv run recipe.py
```

The SIU agent lives in the same folder; add an `ANTHROPIC_API_KEY` to `.env` and run `uv run --group agent agent.py`. Each run writes its queues or its report under `runs/`, so you can read what the recipe decided invoice by invoice. Serverless sends your inputs to Superlinked, which is fine for synthetic claims; when you move to real ones, point `SIE_BASE_URL` at an SIE server in your own environment, and [SELF_HOSTING.md](https://github.com/superlinked/cookbook/blob/main/SELF_HOSTING.md) has the setup. That keeps the task-model calls in your environment, but the SIU agent still calls the Anthropic API, so run it only with data approved for that service.

## Conclusion

Claims work is full of jobs like these three, and you can take any of them apart the same way: write down the steps, run the steps you know as a workflow with small models reading the invoices and calls, check everything they read against the claim you already hold, and give the jobs nobody can script to an agent your workflow checks. The decisions about money and coverage stay with the claims team.

If one of your own invoices trips the recipe up in a new way, I'd like to see it. Until next time.
