Back to blog

Teaching AI to Read Messy Spreadsheets (Without Trusting It Too Much)

If you've ever moved contacts from one app to another, you know the pain. One spreadsheet calls it "Phone," another "Mobile No.," a third "Contact Number." One has "Full Name," another splits it into "First" and "Last." Every tool exports data a little differently.

I built a CSV importer that solves this for a sales CRM (a system businesses use to track their customers and leads). You upload a spreadsheet from anywhere, whether it's Facebook Lead Ads, Google Ads, another CRM, or something typed up by hand, and it figures out which column is which. You can try it live.

I built it as a take-home assignment for a job interview in January 2026, but the problem turned out to be interesting enough that I wanted to write about how I approached it.

Why not just match column names?

The traditional approach is a lookup table: "if the column is called Phone or Mobile, put it in the phone field." It works until someone's spreadsheet says WhatsApp line or Tel (office). You end up writing endless rules and still missing cases.

A person, on the other hand, would glance at the column, see a bunch of numbers starting with +234, and say "that's obviously a phone number." That kind of common-sense reading is exactly what modern AI models are good at. So I let an AI model (Google's Gemini) do that part.

The AI is a smart assistant, not the boss

Here's the most important design decision in the whole project.

AI is great at understanding messy input, but it can occasionally be confidently wrong. So I split the work in two, like an office with an assistant and a manager:

The AI never decides whether a record is good enough to import. That rule lives in ordinary code that behaves the same way every single time. This removes a whole category of "the AI had a bad day" problems.

I also asked the AI to reply in a strict, pre-defined format, so it can't hand back a jumbled answer that breaks the next step.

Working in small batches

A spreadsheet can have thousands of rows. Sending them all to the AI in one go would be slow, and a single hiccup could ruin the whole import.

So the importer works like a team sorting mail. It splits the rows into small bundles of about 20, and handles a couple of bundles at the same time. If a bundle fails, it tries again (up to twice, with a short pause between attempts). And if a bundle still fails after that, only those 20 rows are marked as skipped. The rest of your import carries on. One bad bundle never takes everything else down with it.

Showing progress as it happens

Nobody enjoys staring at a spinning wheel with no idea how long it'll take.

Instead of waiting until everything is finished and sending one giant answer, the server sends results back as each bundle finishes. On screen, you see a progress bar move and a results table fill in row by row. When it's done, you get a summary: how many were imported, how many were skipped, and why.

The flow is simple: upload your file, preview what's in it, confirm, and see the result.

One source of truth

A small but useful detail: the list of CRM fields and allowed values is written down in exactly one place, and every part of the app reads from it. The instructions sent to the AI, the checks that run afterwards, and the screen that shows you the results all use the same list. If the CRM ever adds a new field, it only has to change in one spot, and nothing drifts out of sync.

The takeaway

The lesson I'd pass on to anyone adding AI to a product: let AI do what it's good at, and keep firm rules for everything that must be right. Here, AI handles the messy, human job of understanding what a column means. Ordinary code handles the strict job of deciding what's acceptable. Together, they're more reliable than either would be alone.

Back to blog