← Blog

Process automation

Procesautomatisering: 3.420 pakketuitvallen per week

Het is dinsdag 19:43 in Berchem. De dispatch lead heeft 612 Bpost-regels open, 287 PostNL-regels, douanepapier van drie chauffeurs en een WMS uit 2010.

Jacob Molkenboer· Oprichter · A Brand New Company· 14 jun 2026· 10 min
Open linnen grootboek, drie ijzeren labels op pagina's, een limoengroen lint, koperen weegschaal, rubberen stempel.

Het is dinsdag 19:43 in Berchem, Antwerpen. De dispatch lead heeft het volgende open staan: een Bpost track-and-trace export met 612 regels van vandaag, een PostNL-CSV met 287, een SharePoint-map met douanepapier van drie chauffeurs en een Centric Carelogistics WMS uit 2010 die op het inlogscherm nog altijd 'Internet Explorer 8 of hoger' vermeldt. Morgenochtend om 06:00 gaat het magazijn open. Tegen die tijd moet ze weten welke pakketten door de douane bevroren zijn, welke fout gescand staan, welke met de ochtendrit mee kunnen, en over welke 14 de controllers moeten bellen. Dit is het soort operationele moeras waarin procesautomatisering zichzelf ofwel terugverdient, ofwel de boel stilletjes erger maakt.

Hier liepen we in februari binnen. We bouwden een procesautomatisering-agent die nu 3.420 van dit soort uitvallen per week afstemt en nooit een ticket sluit zonder dat de controller heeft getekend. Dit is wat we hebben geleerd.

Het reconciliatieprobleem van 23:00

De operator verwerkt ongeveer 4.800 pakketten per dag door Vlaanderen en Zuid-Nederland, met een team van 39 mensen. Ongeveer 9% van die pakketten levert op een gegeven week een uitval op: een gemiste scan, een douane-hold (douane-bevroren), een onbestelbaar pakket, een retour, een dubbel label dat zowel Bpost als de WMS in de war stuurt, of een chauffeur die het pakket bij de verkeerde stop scande.

Tot februari deden de dispatch lead en één ops-controller dit allemaal met de hand. Twee tabbladen Excel. Het PostNL-portaal in het ene venster, de Bpost track-and-trace export in het andere, de Centric WMS in het derde. Ze sloten ongeveer 2.400 van de 3.400 wekelijkse uitvallen. De rest schoof door naar de volgende week en stapelde zich op.

De verborgen kost zat niet in de uren. De verborgen kost zat in de douane-achterstand. Een douane-bevroren pakket dat niet binnen 24 uur bij de juiste ambtenaar met de juiste papieren ligt, begint opslagkosten te genereren bij de vervoerder. Over zes maanden in 2025 betaalde de operator zo €11.300 aan opslag die niet betaald had hoeven worden. Niemand had die regel aan een workflow-probleem toegeschreven, tot wij het in kaart brachten.

In een Centric WMS uit 2010

De WMS is een Centric-product dat het team sinds 2010 draait. Het is geen SaaS. Het draait op een Windows Server 2019-VM, praat met een Microsoft SQL Server 2014-instance, biedt een SOAP API op een intern subnet aan, en heeft een web-UI die de operator lang geleden met een eigen CSS-bestand heeft opgepoetst zodat het niet meer naar 2010 ruikt.

Operationeel gezien is dat ding ook gewoon prima. Het volgt elk pakket, elke scan, elke chauffeurstoewijzing, elke douane-hold. De schema's kloppen. De SOAP endpoints werken. Wat het niet doet: zelfstandig praten met Bpost of PostNL, en uitvallen behandelen als first-class objects. Een uitval is in Centric een statusveld op een pakketregel, geen queue.

Dit is het type situatie waar het meeste procesautomatiseringswerk van ons binnenkomt: een legacy systeem dat fundamenteel klopt en niet de moeite van vervangen waard is, met daarnaast twee of drie moderne API's (vervoerders, douaneportalen, een CRM) die erin moeten landen. Het gesprek over de WMS vervangen kost 18 maanden en minimaal €280.000. Het gesprek over de agent kostte zes weken.

De uitval-taxonomie

Voordat er ook maar één regel agent-code geschreven werd, hebben we twee dagen aan het bureau van de dispatch lead en de controller gezeten en elk soort uitval dat hun bureau passeert hardop benoemd. We kwamen uit op 17 categorieën. De agent hoeft alleen de top zes zelfstandig af te handelen. De rest belandt in de queue van de controller.

De taxonomie was belangrijker dan de modelkeuze. Dit is de versie die we hebben uitgerold:

exceptions:
  bpost_customs_hold:        # douane-bevroren, needs officer routing
    auto_route: douane_queue
    sla_seconds: 90
    requires_signoff: true
  bpost_missed_scan:         # parcel exists in WMS, not in Bpost feed
    auto_route: dispatch_queue
    requires_signoff: false
  postnl_undeliverable:      # NL side, retry slot
    auto_route: retry_queue
    requires_signoff: false
  duplicate_label:           # same tracking ID, two parcels
    auto_route: controller_queue
    requires_signoff: true
  driver_misscan:            # wrong stop, detect from GPS
    auto_route: dispatch_queue
    requires_signoff: false
  return_to_sender:          # carrier-initiated
    auto_route: rts_queue
    requires_signoff: true
  unknown:
    auto_route: controller_queue
    requires_signoff: true

Alles wat de agent niet kan matchen aan een bekende taxonomie, komt in controller_queue terecht. Dat is precies de failure mode die we willen. Liever 40 vreemde pakketten op het bureau van de controller, dan één verkeerd geclassificeerde douane-hold.

Hoe de agent afstemt

Om de vijf minuten draait de agent de volgende loop:

  1. Trek de laatste Bpost- en PostNL-status op voor pakketten die in de WMS als 'in transit' staan. Beide vervoerders bieden dat aan via hun developer portalen (bpost.cloud en developer.postnl.nl).
  2. Bereken voor elk pakket het verschil tussen de WMS-status en de vervoerder-status.
  3. Classificeer dat verschil aan de hand van de taxonomie.
  4. Stuur de uitval naar de juiste queue.
  5. Voor categorieën met grote gevolgen (douane, RTS, dubbel label) is expliciete signoff van de controller vereist voordat de agent iets terugschrijft naar de WMS.

De loop is saai. Dat is precies het doel. Operations leads hebben genoeg ademloze agent-demo's gezien. Wat vertrouwen oplevert, is duizend opeenvolgende cycli van vijf minuten waarin de agent steeds hetzelfde saaie ding doet.

De interessante code zit bij de carrier-state diff. Bpost en PostNL gebruiken verschillende statusvocabulaires, verschillende versheidsgaranties en verschillende tijdzoneconventies in hun feeds. PostNL geeft je UTC. Bpost geeft je Brussel-lokaal, behalve in de douane-payload, die in UTC zit. Dat hebben we de trage manier geleerd.

def reconcile(parcel, bpost_state, postnl_state):
    wms = wms_client.get(parcel.id)
    carrier = bpost_state or postnl_state
    if carrier is None:
        return classify_missed_scan(wms)

    if carrier.kind == "customs_hold":
        # bpost payload is UTC even though the rest of bpost is CET
        hold_age = utcnow() - carrier.held_at_utc
        return ExceptionEvent(
            type="bpost_customs_hold",
            parcel_id=parcel.id,
            payload=carrier.douane_payload,
            age_seconds=hold_age.total_seconds(),
            requires_signoff=True,
        )

    if carrier.status_code in WMS_KNOWN_STATUSES:
        if carrier.status_code != wms.status_code:
            return ExceptionEvent(
                type=classify_status_diff(wms, carrier),
                parcel_id=parcel.id,
            )
    return None

Vier weken in shadow mode

Een agent die met een legacy WMS praat, verdient niet op dag één vertrouwen. De eerste twee weken draaide de agent in shadow mode: hij stemde elke uitval af, classificeerde elk event, maar schreef niets terug naar Centric. De controller zag de voorgestelde queue van de agent naast haar eigen werk staan. Elke ochtend bekeek ze de verschillen. Tegen het einde van week twee had de controller 47 meningsverschillen gemarkeerd. We hebben er 41 in code rechtgezet en de overige zes naar de unknown-queue gestuurd voor permanente menselijke review.

In week drie en vier lieten we de agent terugschrijven, maar alleen voor de vier categorieën met de minste impact: gemiste scans, chauffeursmisscans, onbestelbaren en retries. Douane-holds, RTS en dubbele labels bleven nog twee weken in shadow. Tegen week zes draaide de volle loop live, en had de controller genoeg tijd doorgebracht met de redenering van de agent om hem ook op douane te vertrouwen. Sindsdien hebben we dezelfde shadow-naar-live aanloop op elke procesautomatisering-agent gedraaid. De kost is twee weken langzamere terugverdientijd, en een permanente verlaging van de kans dat je een zelfverzekerde, foute agent oplevert.

Waarom de controller blijft tekenen

De niet-onderhandelbare regel was vanaf dag één: de agent sluit nooit een ticket dat de controller niet heeft goedgekeurd. Niet voor douane. Niet voor retours. Niet voor iets waar de operator tegenover een derde partij voor opdraait.

Inzicht

Een automatisering-agent die stilletjes tickets sluit, koopt snelheid en verliest binnen een week het vertrouwen van de controller. Zet de mens bij signoff, niet bij data-invoer.

Wat de controller 's ochtends ziet is geen lijst van 612 ruwe uitvallen. Het is een queue van 14 voorgeclassificeerde, voorgerouteerde, voorgesamenvatte tickets, elk met de relevante vervoerder-payload, de WMS-regel, en één enkele knop: goedkeuren en terugschrijven, of weigeren en herclassificeren. De gemiddelde signoff-tijd in haar queue ging van 4,5 minuut per ticket naar 38 seconden.

De agent schrijft ook niets rechtstreeks naar de SQL database. Elke WMS-write loopt via de SOAP endpoint, op dezelfde manier waarop een menselijke gebruiker dat zou doen, met de sessietoken van de controller. In het auditlog van Centric staat de controller nog steeds als degene die de actie deed. Dat hebben we bewust zo gebouwd. Als de auditor langskomt, is 'de AI deed het' geen antwoord.

Douane-bevroren pakketten binnen 90 seconden routeren

De douanecasus is de casus die zichzelf terugverdient. Zodra een Bpost-feed een pakket op HELD_FOR_CUSTOMS zet, moet de agent:

  1. De douane-payload van Bpost ophalen (de TARIC-goederencode, aangegeven waarde, afzender, ontvanger).
  2. Hem matchen tegen de douanier-roster van de operator (de Antwerpse haven heeft vier ambtenaren waarmee het team werkt).
  3. Bepalen welke ambtenaarqueue het pakket krijgt, op basis van goederentype en huidige belasting van de ambtenaar.
  4. De vooringevulde douanepapieren in de queue van de ambtenaar droppen.
  5. De dispatch lead via Slack waarschuwen zodat een koerier kan worden herpland.

Vanaf het moment dat de Bpost-feed de status omklapt, ligt het pakket binnen 90 seconden in de queue van een ambtenaar. De oude loop met alleen mensen deed er gemiddeld 6 tot 14 uur over, afhankelijk van of de dispatch lead actief meekeek. De douane-opslagkosten daalden over de eerste tien weken van een runrate van ongeveer €1.900 per maand naar €260.

De ambtenaarqueue zelf is niet glamoureus. Het is een Postgres-tabel, een kleine Next.js-pagina op het interne subnet, en een Telegram-bot voor de ambtenaar buiten kantooruren. Niets in deze stack zou indruk maken in een Hacker News-thread. Het werkt omdat de saaie stukken correct getekend zijn.

Resultaten na drie maanden

Cijfers uit de eigen rapportage van de operator, periode 01-03-2026 tot 31-05-2026:

  • 3.420 uitvallen per week gemiddeld afgestemd, tegenover een basislijn van 2.400.
  • Eind-van-de-week backlog terug van gemiddeld 980 naar 11.
  • Douane-opslagkosten 86% omlaag.
  • De avonddienst van de dispatch lead 1,7 uur per dag korter.
  • Nul automatisch gesloten douane-tickets. Elk getekend door een controller.

Het cijfer dat we niet hebben verbeterd, is het foutpercentage van de vervoerders zelf. Bpost en PostNL produceren nog steeds hetzelfde volume aan uitvallen. We hebben de vervoerders niet gerepareerd. We hebben gerepareerd wat er gebeurt nadat de vervoerders falen.

Wat we anders zouden doen

Twee dingen die we in de eerste versie verkeerd hadden.

Ten eerste hadden we de controller-signoff aanvankelijk als één dagelijkse digest om 07:00 ingericht. Dat was fout voor douane. Een douane-hold om 09:00 hoort niet tot de volgende ochtend te blijven liggen. In week drie hebben we de queue gesplitst in 'vandaag tekenen' (douane, RTS, duplicaten) en 'deze week tekenen' (de rest). Een dagelijkse digest is prima voor het meeste ops-werk, en fout voor alles wat tijdgebonden is.

Ten tweede onderschatten we hoeveel zichtbaarheid de controller nodig had. De eerste versie van de agent draaide in stilte. De controller vroeg, terecht, 'hoe weet ik dat hij loopt?'. We hebben een statuspagina toegevoegd, daarna een log per loop, daarna een spoor per pakket. Als je een agent uitrolt tegen verouderde infrastructuur en je hebt de operator geen manier gegeven om te zien wat de agent deed en waarom, dan heb je een half product opgeleverd. De eerste keer dat er iets misgaat, moet je het regel voor regel uitleggen, en die regels heb je niet.

Wat je vandaag kunt doen

Toen we deze uitval-afstemmingsagent bouwden voor de Antwerpse operator, zat de doorbraak niet in het model. Hij zat in twee dagen aan het bureau van de controller zitten en elk soort uitval hardop benoemen. De meeste operations-teams kunnen die lijst niet in tien minuten uitspreken. Dat gat is het werk. Wil je weten of jouw bedrijf klaar is voor procesautomatisering, doe dan eerst de taxonomie van twee dagen.

Open een spreadsheet. Lijst elke uitval die je team afgelopen week heeft verwerkt. Groepeer ze. De categorieën die vijf keer of vaker langskomen, zijn werk voor de agent. De categorieën die één keer voorkomen, zijn werk voor de controller. Daar blijft de agent vanaf.

Kern

Een automatisering-agent die stilletjes tickets sluit, koopt snelheid en verliest binnen een week het vertrouwen van de controller. Zet de mens bij signoff, niet bij data-invoer.

FAQ

Waarom hebben jullie de 16 jaar oude Centric WMS niet gewoon vervangen in plaats van er een agent bovenop te bouwen?

Vervangen was een gesprek van 18 maanden en €280.000. Het schema en de SOAP API van de WMS klopten. Het gat zat in de carrier-integratie en het uitvalbeheer, en dat kon een agent in zes weken dichten.

Sluit de agent ooit een douane-ticket zonder dat een mens kijkt?

Nee. Douane, retours-naar-afzender en dubbele labels vereisen allemaal expliciete signoff van de controller voordat de agent terugschrijft naar de WMS. Het audit-spoor in Centric toont de controller als degene die de actie deed.

Hoe weet de agent welke douanier een bevroren pakket moet krijgen?

Hij matcht de Bpost-douanepayload (TARIC-goederencode, aangegeven waarde, afzender) tegen de roster van douaniers en de huidige belasting per douanier. De beslissing landt binnen 90 seconden in de queue.

Wat was na drie maanden de grootste operationele winst?

Douane-opslagkosten daalden 86%, van ongeveer €1.900 per maand naar €260. Snellere routing van douane-bevroren pakketten naar de juiste ambtenaar was de hefboom. Niets anders kwam daar op de winst-en-verliesrekening bij in de buurt.

process automationai agentscase studyintegrationsoperations

Iets bouwen?

Start een project