← Blog

RAG

RAG voor KvK-uittreksels: Bossche fiscalist als case

Een fiscaal kantoor van zeventien mensen bij Den Bosch hield vier parttimers bezig met het overtypen van KvK-uittreksels in Yuki. Een RAG-agent doet nu elk dossier in 90 seconden.

Jacob Molkenboer· Oprichter · A Brand New Company· 8 jun 2026· 9 min
Open eiken kaartenbak op ivoorpapier, één kaart naar voren met groen tabje, messing scheider, gevouwen document ernaast.

Woensdagmiddag bij een fiscaal kantoor vlak bij station Den Bosch. Het kantoor draait op rustige open werkplekken, twee espressomachines en vier parttimers die samen de inhoud van KvK-uittreksels overtypen in Yuki, goed voor negentig tot honderd nieuwe dossiers per week. Een zorgvuldig iemand doet er acht tot twaalf minuten over. Een typefout kost veertig.

Het kantoor heeft zeventien mensen. Twee partners, vijf seniors, zes juniors, drie back-office. De vier parttimers vallen onder 'back-office', maar kosten net zoveel als een senior, en vertrekken om de negen maanden, want adressen overtypen voor je werk is niet waar mensen voor tekenen. De managing partner had nog vier mensen aangenomen. Niemand kwam de proeftijd door.

Toen we in februari met ze om tafel gingen was de vraag klein en scherp: kan software een gescand KvK-uittreksel lezen en de nieuwe relatie in Yuki zetten zonder dat er een mens tussen zit? Het antwoord was bijna ja.

Waarom KvK-uittreksels zo lastig zijn

Een KvK-uittreksel is een PDF van een tot vijf pagina's van de Kamer van Koophandel met de juridische identiteit van een onderneming: KvK-nummer, RSIN, statutaire naam, handelsnamen, bezoek- en postadres, rechtsvorm, bestuurders, bevoegdheden (alleen, gezamenlijk, beperkt bevoegd), en op nieuwere uittreksels de UBO-aanduiding.

Drie dingen maken ze een slechte match voor naïeve OCR plus een regex.

Ten eerste: de helft is gescand. Klanten van het kantoor mailen PDF's die zijn afgedrukt op een inkjet, gescand op een Brother MFC en opnieuw opgeslagen via de preview van Outlook. De resolutie ligt tussen 150 en 240 DPI. De scheefstand tussen nul en vier graden. Tesseract krijgt de lopende tekst goed, maar de tabelcellen niet.

Ten tweede: de lay-out is geen formulier. KvK-uittreksels zijn opgemaakt als juridisch document. Bestuurder-secties herhalen zich met een wisselend aantal velden. Een BV met twee bestuurders ziet er structureel anders uit dan een stichting met een vijfkoppig bestuur. Een filiaal van een Maltese moeder heeft weer een ander kopblok. Er is geen PDF-formulierlaag om op terug te vallen.

Ten derde: de data heeft gevolgen. Een verkeerd RSIN op een Yuki-administratie breekt de koppeling naar de aangifteflow verderop. Een verkeerde bevoegdheid bij een bestuurder zorgt voor een compliance-gat dat een accountant uiteindelijk in een partner-meeting moet uitleggen.

De vier parttimers waren niet langzaam omdat ze slecht konden typen. Ze waren langzaam omdat ze moesten nadenken.

Architectuur: OCR, retrieval, extractie, post

De pipeline heeft vier stappen, elk met één taak.

# pipeline.py: one dossier, four steps
from pathlib import Path
from app.ocr import ocr_pdf
from app.retrieval import similar_dossiers
from app.schema import KvKExtract
from app.extractor import extract_structured
from app.yuki import YukiClient

REVIEW_THRESHOLD = 0.92

def process(pdf: Path) -> dict:
    pages = ocr_pdf(pdf)                            # ~3s, Dutch Tesseract + deskew
    examples = similar_dossiers(pages.text, k=4)    # ~400ms, pgvector cosine
    extract: KvKExtract = extract_structured(
        text=pages.text,
        examples=examples,
    )                                               # ~12s, structured output
    if extract.confidence < REVIEW_THRESHOLD:
        return {
            "status": "needs_review",
            "reason": extract.flagged_fields,
            "dossier": extract.model_dump(),
        }
    yuki = YukiClient.from_env()
    contact_id = yuki.contacts.upsert(extract.to_yuki_contact())
    return {"status": "posted", "contact_id": contact_id}

Stap één is OCR. Elke pagina gaat door Tesseract met het Nederlandse taalpakket, op paginaniveau rechtgezet door OpenCV. Bij pagina's met lage confidence vallen we terug op een cloud-vision API. Kosten zijn ongeveer €0,003 per dossier aan cloud-vision plus twaalf seconden CPU.

Stap twee is retrieval. Het kantoor heeft ongeveer 2.800 historische dossiers, elk een paar (ruwe KvK-tekst, definitief Yuki-contact), soms met een menselijke correctie ertussen. Die paren staan als embedding in een pgvector-tabel. Voor een nieuw uittreksel halen we de vier dichtstbijzijnde buren op via cosine similarity. Die retrieval-laag is wat dit een RAG-systeem maakt in plaats van een one-shot extractor. Het model ziet hoe een eerder, structureel raar dossier (bijvoorbeeld een CV met een buitenlandse beherend vennoot) uiteindelijk in Yuki belandde.

Stap drie is structured extraction. Het model wordt gedwongen in een Pydantic-schema. Het schema weigert een RSIN die niet matcht met ^\d{9}$, een rechtsvorm die niet in de Nederlandse enum staat, een bestuurder zonder geboortedatum. Een confidence-score onder de 0,92 stuurt het dossier naar de review-queue voor de mens.

# schema.py: Pydantic v2
from typing import Literal
from pydantic import BaseModel, Field

LegalForm = Literal[
    "BV", "NV", "VOF", "CV", "Eenmanszaak",
    "Stichting", "Vereniging", "Maatschap", "Filiaal",
]
Authority = Literal["alleen", "gezamenlijk", "beperkt", "geen"]

class Address(BaseModel):
    street: str
    house_number: str
    postal_code: str = Field(pattern=r"^\d{4} ?[A-Z]{2}$")
    city: str
    country: str = "NL"

class Director(BaseModel):
    full_name: str
    born_on: str = Field(pattern=r"^\d{4}-\d{2}-\d{2}$")
    authority: Authority
    is_ubo: bool

class KvKExtract(BaseModel):
    kvk_number: str = Field(pattern=r"^\d{8}$")
    rsin: str | None = Field(default=None, pattern=r"^\d{9}$")
    statutory_name: str
    trade_names: list[str] = []
    legal_form: LegalForm
    visiting_address: Address
    postal_address: Address | None = None
    directors: list[Director]
    confidence: float = Field(ge=0.0, le=1.0)
    flagged_fields: list[str] = []

Stap vier is de Yuki-post. Yuki heeft een webservice uit het SOAP-tijdperk die de Domain API heet. Niet mooi, maar stabiel, en hij verwacht een Contact-envelope met ContactCode, ContactType, adressen en een lijst contactpersonen. We zetten er een kleine Python-client omheen en vangen de drie foutsoorten af die we in productie zien: contact bestaat al, session is verlopen, BTW-nummer is ongeldig.

Er zit nergens modelmagie in dit verhaal. Het meeste werk zat in de leidingen: de OCR-pipeline, de retrieval-index, de queue, de Yuki-client, de review-UI. Het model is een onderdeel, niet het product.

De retrieval-laag die zijn naam verdiende

Een redelijke lezer vraagt zich af of retrieval überhaupt nodig was. KvK-uittreksels zijn een publiek formaat. Kunnen het schema en een sterke system prompt het werk niet alleen af?

We hebben het geprobeerd. De schone baseline (geen retrieval, alleen een zorgvuldige system prompt) haalde 88% straight-through op een holdout van 200 dossiers. De vier parttimers haalden 96%. Het gat zat in de edge cases.

Een Nederlandse CV (commanditaire vennootschap) heeft een beherend vennoot en een of meer commanditaire vennoten. De vennoten hebben operationeel geen zeggenschap, maar staan wel op het uittreksel. Een naïeve extractie zet ze als bestuurder neer. Een retrieval over oude dossiers brengt drie eerdere CV's naar boven waarin een senior de vennoten eruit had gehaald en een notitie had toegevoegd in het vrije tekstveld van Yuki. Met die voorbeelden in de prompt leert het model de conventie van het kantoor.

Een stichting die aandelen houdt in een STAK-structuur heeft dezelfde UBO-complicatie. Retrieval haalt de voorkeursmapping van het kantoor naar Yuki boven water.

Een filiaal van een buitenlandse moeder heeft geen RSIN. Retrieval laat het model zien dat het kantoor het RSIN-veld leeg laat en het belastingnummer van de moeder in een custom Yuki-veld zet.

Elk van deze gevallen ging zonder retrieval mis. Met vier voorbeelden in de context gaat het goed.

Kern

Het lastige aan een extractie-agent in productie is niet het model. Het is de laag met huisconventies. RAG is hoe je de agent jullie conventies leert zonder ze allemaal op te schrijven.

Posten naar Yuki zonder de boekhouding te slopen

De Yuki-API is ouder dan de UI. Hij gebruikt session-token-authenticatie, geeft XML terug, en behandelt de meeste write-operaties alleen als idempotent als je zelf een ContactCode aanlevert. Drie regels die we na de eerste maand vastlegden.

We genereren ContactCode uit het KvK-nummer: KVK-12345678. Daarmee is de upsert echt idempotent over retries heen, en is het contact in de Yuki-UI vindbaar zonder dat iemand een nieuw ID-schema hoeft te leren.

We posten nooit in dezelfde request als de extractie. De extractie kan vijftien seconden duren. De Yuki-post één. We zetten het op een queue. Een mislukte Yuki-post triggert nooit opnieuw de extractie.

We schrijven altijd de originele PDF naar Yuki als Document bij het contact, ook als de gestructureerde velden netjes binnenkomen. Mocht het model over negen maanden iets subtiels fout hebben gedaan, dan is de bron één klik weg. Dit was de ene ontwerpkeuze waar de partners op stonden. Ze hadden gelijk.

Wat 90 seconden je oplevert

Cijfers, na ongeveer vier maanden in productie.

Gemiddelde wall-clock per dossier: 86 seconden, waarvan 12 op het model en de rest op OCR, retrieval, Yuki-round-trip en queue-overhead.

Dossiers per week: 110, van 95. Twee redenen. Eén: de bottleneck was de beschikbaarheid van de parttimers, dus de vraag werd stilletjes afgeremd door het tempo waarin verwijzingen werden geaccepteerd. Twee: de intake wordt niet meer op dinsdag- en donderdagmiddag in batches gedaan.

Straight-through-rate: 94%. De overige 6% gaat naar een review-queue die een van de seniors in twintig minuten per dag wegwerkt.

Headcount: de vier parttime data-entryrollen zijn niet opnieuw ingevuld toen de laatste in april uitstroomde. Eén van hen, degene die het werk leuk vond, kreeg een traject als junior boekhouder aangeboden. Ze pakte het.

Bouwkosten: lage vijf cijfers. Draaikosten: ongeveer €60 per maand aan vision-API plus een kleine VPS voor de worker.

Dit zijn echte cijfers van één kantoor. Het is geen benchmark. Een groter kantoor met rommeligere intake (handgeschreven brieven, scheef gefotografeerde uittreksels van een telefoon) haalt niet dezelfde straight-through-rate zonder meer retrieval-voorbeelden en een langere human-in-the-loop-periode.

Waar het nog misgaat

Twee soorten fouten die we nog niet hebben opgelost.

Uittreksels die met een telefoon onder een hoek van vijftien graden en met ongelijk licht zijn gefotografeerd, duwen de OCR-confidence onder het punt waar de structured extractor het nog kan redden. Die sturen we nu terug naar de klant met een mail van één regel: graag platte scan. Ongeveer 3% van de intake.

Uittreksels van vóór 2019 gebruiken een iets andere lay-out voor het bevoegdhedenblok. We hebben er elf in de productie-faillogs. De fix is ze aan het retrieval-corpus toevoegen. Dat hebben we nog niet gedaan, want elf is nog niet vervelend genoeg.

Er is een derde fout die niet technisch is. De eerste maand vertrouwden twee seniors de agent niet. Ze typten uittreksels die al gepost waren, opnieuw over in een eigen spreadsheet, om te vergelijken. De fix was geen betere software. De fix was een wekelijkse review-meeting voor de eerste acht weken, waarin de partner de review-queue liet zien, elk gevlagd dossier doorliep en uitlegde welke keuze de agent had gemaakt. Vertrouwen bouw je zoals je vertrouwen bouwt.

Wat we hebben geleerd

De winst bij dit kantoor zat niet in het model. Het model is een paar honderd regels prompt en een Pydantic-schema. De winst zat in het retrieval-corpus (2.800 schoon gelabelde oude dossiers die het kantoor al had, in hun eigen Yuki-account), in de confidence-drempel (conservatief gezet, daarna losser), en in de discipline om bij elk contact de bron-PDF te hangen, zodat er nooit iets verloren ging.

Heeft jouw kantoor een wachtrij met gestructureerd-data-overtypen-uit-PDF, dan is de audit van vijf minuten deze: tel de dossiers per week, vermenigvuldig met de gemiddelde minuten per dossier, deel door 60. Komt de uitkomst boven de werkuren van één persoon, dan begint de rekensom richting een agent te kantelen. Heb je daarbij ook nog een gelabeld archief van oud werk, dan is de rekensom eenzijdig.

Toen we dit voor het kantoor in Den Bosch bouwden, zat het lastige niet in de extractie, maar in de Yuki Domain API en in de conventies die alleen de partners kenden. Zo lossen we het meeste werk met AI-agents op: door het model als het goedkope deel te zien en de huiskennis als het echte bezit.

De audit kost vijf minuten. Open je operations-dashboard, tel de nieuwe-klantdossiers van vorige maand, en vermenigvuldig met je werkelijke kosten per minuut back-office-tijd. Het getal is meestal hoger dan mensen verwachten.

Kern

Het lastige aan een extractie-agent in productie is niet het model. Het is het retrieval-corpus dat de conventies van jouw kantoor vastlegt.

FAQ

Wat is een KvK-uittreksel?

Een PDF van de Kamer van Koophandel met de juridische identiteit van een onderneming: KvK-nummer, RSIN, statutaire naam, adressen, rechtsvorm en bevoegde bestuurders. Het merendeel komt gescand binnen, waardoor naïeve extractie onbetrouwbaar is.

Waarom RAG en niet één extractie-call?

Retrieval haalt oude dossiers boven met vergelijkbare edge cases (CV-vennoten, STAK-stichtingen, buitenlandse filialen), zodat het model de conventies van het kantoor leert zonder dat iemand ze hardcodeert in de prompt.

Wat gebeurt er als het model niet zeker is van een veld?

Een confidence-score onder de 0,92 stuurt het dossier naar een review-queue voor een mens. De originele PDF hangt aan elk Yuki-contact, dus een stille fout is later altijd terug te halen.

Hoe lang duurt het om dit uit te rollen bij een kantoor van 10 tot 30 mensen?

Vier tot zes weken als je al een gelabeld archief hebt van eerdere dossier-naar-systeem-mappings om retrieval mee te starten. Langer als je het corpus van nul moet opbouwen.

ragai agentsprocess automationcase studyintegrationsoperations

Iets bouwen?

Start een project