Macro data-analyse

Welke klanten kosten meer dan ze opleveren?

Administratiekantoor Berkhout doet voor vijfendertig klanten de hele administratie voor een vast bedrag per jaar. Zes mensen, drie jaar aan geschreven uren. Het antwoord op die vraag zit in de urenregistratie — alleen kwam er niemand bij.

Berkhout is een simulatieopdracht: een kantoor dat wij zelf hebben opgezet om deze verwerking op te bouwen en na te meten.

geschreven urenregels
21.093
schrijfwijzen voor 35 klanten
140
klanten die drie jaar op rij geld kosten
3
doorspitten.py
roept AI aan
Urenregistratiedrie jaar, zes mensen20.813na opschonen6.694letterlijk uniek3.342na normaliserenzes keer minder werk voor het modelWerksoortbepalenClaude Haiku 4.5Klantnamen140 schrijfwijzenOnderscheidende woorden vergelijkencode, geen model35klanten · 100% juistTerug op alleregels3.342 antwoorden → 20.813 regelsOptellenvragen.sqlMarge per klantHerstelwerk per klantNiet gefactureerdEén van de zeven stappen roept AI aan.

De gegevens waren er al. Er was alleen niet bij te komen.

Dit is geen bijzonder geval. Vrijwel elke urenregistratie die langer dan een jaar meegaat heeft dezelfde drie gebreken: de klantnaam is nooit afgesproken, er staat niet bij wat voor werk het was, en de registratie zelf zit vol regels die niet kunnen.

Het gevolg is dat je wel kunt zien dát een klant veel uren kost, maar niet waaraan. En dat is precies het verschil tussen „we moeten het tarief verhogen” en „we moeten iets afspreken over wat je aanlevert”.

De opdracht was dus niet om een model op de data te zetten. De opdracht was om er één vraag uit te krijgen die klopt, en om te kunnen laten zien hoe goed het antwoord is.

Zes dingen waar een draaitabel op stukloopt.

Geen van deze zes is exotisch. Ze zitten in vrijwel elke urenregistratie, en ze zitten daarom allemaal in de testdata.

Vier namen voor één klant

Groothandel Vlietstra staat in het systeem als „Groothandel Vlietstra”, „Vlietstra groothandel”, „Vlietstra” en „gh Vlietstra”. Een draaitabel op klantnaam geeft dus honderdveertig klanten in plaats van vijfendertig.

„Vos” lijkt nergens op

Timmerwerken Vos wordt ook gewoon „Vos” genoemd. Tekstueel lijken die twee vrijwel niet op elkaar, dus wie op gelijkenis matcht raakt die klant kwijt. Ze delen wel het enige woord dat ertoe doet.

„Overleg”, en verder niets

Datzelfde woord is een gesprek met de klant of een intern werkoverleg. Het verschil staat niet in de omschrijving maar in de vraag of er een klant aan de regel hangt.

Eén op de zeventien regels zegt niets

„Diversen”, „werkzaamheden”, een streepje, of helemaal leeg. Die regels bestaan en de uren erop zijn echt. Ze moeten dus meetellen zonder dat er een verzonnen werksoort onder komt.

Herstelwerk lijkt op gewoon werk

„Afwijking balans uitgezocht” is geen jaarwerk maar herstelwerk. Wie dat verschil niet ziet, mist precies de categorie waar in deze dataset het antwoord in bleek te zitten.

De registratie zelf is niet schoon

Regels van vierentwintig uur, regels van nul uur, en dezelfde regel twee keer omdat iemand twee keer op verzenden drukte. In deze drie jaar 196 en 84 stuks.

Twee verliezen die er hetzelfde uitzien.

Drie klanten kosten drie jaar op rij meer dan ze opleveren. Dat was de vraag. Het bruikbare antwoord staat ernaast: over het hele kantoor is negen procent van de kosten herstelwerk — corrigeren, uitzoeken, opnieuw doen. Dat is de normaal waar je de rest tegen afzet.

Talsma: − € 19.706

21 procent van de kosten is herstelwerk, ruim twee keer de normaal. Over drie jaar € 22.812 aan werk waar geen afspraak tegenover staat — meer dan het verlies zelf. Zonder dat herstelwerk is deze klant gewoon winstgevend.

Vlietstra: − € 14.880

Herstelwerk op 7 procent, onder het gemiddelde. Hier is niets bijzonders aan de hand behalve het bedrag: het is te veel werk voor wat er is afgesproken. Ander probleem, ander gesprek.

Kolthoff: + € 48.563

Staat op geen enkele lijst van probleemklanten, en terecht. Er zit alleen wel € 22.704 aan herstelwerk in, achttien procent van alles wat er voor die klant gedaan wordt. Zonder werksoort onder de uren is dat onzichtbaar.

Twee klanten die in hetzelfde overzicht naast elkaar staan, met twee gesprekken die niets met elkaar te maken hebben. Dat onderscheid is het hele punt van deze opdracht. De percentages hierboven zijn wat de verwerking zelf oplevert, en die zijn eerder te laag dan te hoog — hoeveel precies staat verderop.

Elke stap zegt wat hij heeft weggegooid.

Een schoonmaakstap die niet vertelt wat hij heeft weggegooid, is een schoonmaakstap die je niet kunt controleren. Daarom telt elke stap zijn eigen resultaat mee en komt dat allemaal in één bestand terecht.

Dit is de uitvoer van de run waar alle cijfers op deze pagina uit komen. Het is één script van zeven stappen; er zit geen dashboard omheen en dat hoeft ook niet.

python doorspitten.py
1. ingelezen          21093 regels, 35 klanten
2. opgeschoond          196 onmogelijke uren, 84 dubbele regels weg -> 20813 over
3. klanten gekoppeld  19055 regels, 140 schrijfwijzen -> 35 klanten
4. sleutels           20813 regels -> 6694 letterlijk uniek -> 3342 na normaliseren
5. gelabeld            3342 opgevraagd, 0 uit cache, 79.6s, $0.42
6. gecontroleerd    klantkoppeling 100.0% juist
   model          95.9% juist bij 93.3% dekking = 89.5% van alle regels
   woordenlijst   96.1% juist bij 89.4% dekking = 85.9% van alle regels
7. geanalyseerd     6 vragen op 20813 regels

Waar het hier om draait.

Van de zeven stappen roept er één AI aan. Dat is geen zuinigheid maar een ontwerpkeuze: code is goedkoper, geeft twee keer hetzelfde antwoord op dezelfde invoer, en valt na te rekenen.

I

Klantnamen koppelen zonder model

Per officiële klantnaam de woorden bewaren die die klant onderscheiden van alle andere, en een geschreven naam koppelen aan de klant waar de meeste van die woorden in voorkomen. Pas als dat niets oplevert beslist de tekstgelijkenis. Gemeten resultaat: 100% juist over alle 20.813 regels.

II

Niet 20.813 vragen stellen maar 3.342

Het aantal verschillende dingen dat mensen opschrijven is veel kleiner dan het aantal keren dat ze het opschrijven. Cijfers en leestekens eruit, en „btw aangifte Q3” en „btw aangifte Q1” zijn dezelfde vraag. Zes keer minder werk voor het model, met dezelfde uitkomst.

III

„Onbekend” is een goed antwoord

De opdracht aan het model zegt met zoveel woorden dat twijfelen mag. Een gokje dat er stellig uitziet verdwijnt ongemerkt in de optelling en verschuift het antwoord; een eerlijk „onbekend” blijft zichtbaar en kan apart bekeken worden.

IV

De analyse zelf is gewoon SQL

Zodra er een werksoort onder de uren hangt, is de rest optellen en delen. Dat staat in een apart bestand dat los te lezen en los te draaien is, zodat de boekhouder het desnoods zelf narekent. Daar hoort geen model bij.

Was het model hier eigenlijk wel nodig?

Naast het model draait een woordenlijst mee: ongeveer dertig zoekwoorden, wat een ontwikkelaar in een middag schrijft. Die is er niet om te verliezen. Als de woordenlijst goed genoeg is, is het model overbodig, en dan hoort dat hier te staan.

Over alle 20.813 regels heeft het model er 89,5 procent goed en de woordenlijst 85,9 procent. Het model is niet preciezer — het durft alleen vaker een antwoord te geven in plaats van „onbekend”. Dat kostte $ 0,42 en tachtig seconden.

En op het punt waar het hier om ging, maakt het vrijwel niets uit. Van het herstelwerk dat er werkelijk in zit vindt de woordenlijst 66 procent en het model 68 procent. Allebei missen ze een derde, en om dezelfde reden: „afwijking balans uitgezocht” ziet eruit als jaarwerk, ook voor een model, want nergens staat dat er iets fout was. Dat is geen tekortkoming van het model maar van de vraag.

Het advies aan Berkhout is daarom niet om dit model in productie te nemen.

Zet een vinkje „dit is herstelwerk” in het urenscherm. Dan meet je het vooraf in plaats van het achteraf te raden, en staat er volgend jaar geen 68 procent maar 100. Het model heeft zijn werk dan al gedaan: het heeft drie jaar oude uren bruikbaar gemaakt, zodat je wist dát dit de vraag was.