Skip to content
MP

The inventory had to learn to read

I built a tool to check a BOM against my parts inventory before ordering. It worked, and then it didn't — because my own data was written in a dozen dialects.

Every board I build starts with the same dull question: of the eighty lines in this BOM, which ones do I already have in a drawer?

Three hundred and ninety-one parts in the inventory. A BOM export from the EDA tool. The honest answer used to be that I did not check properly — I skimmed, ordered what I was unsure about, and accumulated a third bag of 100nF 0603 capacitors.

Today I built the thing that answers it. It took thirty typed prompts across two sessions and eighteen commits, and the interesting half was not the matching.

The reconciler

The first session was straightforward. Read the BOM, read the inventory, match each line, colour it green, amber or red, and let me tick the ones I actually want to order.

The BOM Reconciler upload screen: a file drop zone reading 'Click to choose or drag a BOM file here, CSV or TSV, UTF-8 or UTF-16', with a status line confirming 80 lines loaded and 9 of 9 known columns recognised.
Drop the CSV the EDA tool exports. Nine of nine columns recognised — the point is that I never reformat anything by hand.

Matching a BOM line to a part is tiered. A manufacturer part number is the strongest signal, then a supplier code, then value-plus-package, then a fuzzy token match on the description. Quantity is never considered — my stock counts are unreliable and I know it, so letting a quantity affect a match status would launder a guess into a fact.

That much worked by mid-morning. Then I asked it to do a relaxed second pass on everything still red, and to group lines that describe the same component — the EDA tool does some of this, but leaves C50 and C51 as separate rows when they are the same 100nF part. On my current mainboard that merging takes eighty source lines down to seventy-one rows.

The BOM reconciler results: counters reading 71 total, 48 green, 0 amber, 23 red, 69 distinct, 20 undecided, 2 to order, 1 substituted, above a table of BOM rows with order checkboxes, status pills and designator lists.
Seventy-one rows: forty-eight already in stock, twenty-three not. The checkbox is pre-ticked for anything missing, and the count of what I have actually decided is on the face of it.

Forty-eight green, twenty-three red. That is the answer to the dull question, and it took until today to have it.

Where it stopped working

The matcher was fine. The data underneath it was not.

The three hundred and sixty rows I had at that point had grown a hundred and two different spellings of the package field. TO220 and TO-220. DIP8, DIP-8 and dip. SOP4 and SOP-4. R2512 and 2512. module, Module and MODULE. Fifteen said SMD, which describes how a part attaches to a board and tells you nothing whatsoever about its footprint.

The name field was worse. Rows I had typed by hand read 100n, 10R, UC3845 — short, searchable, the way I actually think about a part. Rows I had pasted from a supplier order read:

AC0603FR-0747KL-YAGEO-Res Thick Film 0603 47K Ohm 1% 0.1W(1/10W)
±100ppm/°C Pad SMD T/R Automotive AEC-Q200

That entire string, in the name field. And again in the details field, because the paste parser filled both from the same text.

No amount of cleverness in the matcher fixes this. Value-plus-package matching needs a package it can compare, and a fuzzy token match against a hundred-and- twenty-character supplier blob drowns in noise. The tool was reading data I had made unreadable.

Two classifiers

So the afternoon was spent teaching software to read a supplier description the way I do.

The first classifier extracts the footprint. There was already a regex doing this in the paste parser, and against a real thirty-one line order from Robu it got fourteen right — and several of those fourteen were wrong rather than merely missing. SMD Chip Resistor ... 1206 came out as SMD, because SMD appeared earlier in the string and the regex took the first match. SOT-23-5 was truncated to SOT-23 for the same reason.

Two rules fixed it. Score candidates by how much text they match, not where they appear, so 1206 beats SMD and SOT-23-5 beats SOT-23. And treat a mount style as not-an-answer: SMD, THT and Surface Mount now produce an empty field and a flag, never a value.

That second rule matters more than it sounds. A blank field with a warning dot is honest — I fill it in. SMD written into the package column looks like data and is useless for matching, forever. The classifier now gets all thirty-one lines right, and seven of those thirty-one are right because it declines to guess.

The second classifier derives the short name, and it is harder, because the rule inverts by component type. An IC is named by its part number, with the vendor and packaging codes stripped: ADR01BRZ-REEL7-ANALOG DEVICES-Voltage Reference... becomes ADR01BRZ. A resistor is named by its value and never by its part number — nobody has ever searched their drawer for a Yageo order code.

The Add Parts queue after pasting a supplier order: rows reading BAV199 with a SOT-23 pill, SL8552XS8 with SOP-8, SL9061A with SOT-23-5, SN74AVC8T245PWR with TSSOP-24, and ADR01BRZ with SOIC-8. One row, FocuSens Ring Terminal NTC BMS Temperature Sensor, carries an amber dot and no package pill.
Both classifiers on a real order paste. Note SL9061A kept its SOT-23-5 rather than being truncated to SOT-23, and the FocuSens sensor has an amber dot and no package — the description names none, so none was invented.

That screenshot is the whole argument in one frame. Five rows resolved to a part number and a footprint. One row — a ring-terminal thermistor whose description contains no part number and no package — kept its full name, got no package, and earned an amber dot instead. Thirty-one lines pasted, ready to file into bins.

What my own data told me

Before writing either classifier I told Claude the convention: passives get value plus rating, so 4.7k 250mW.

Then we benchmarked the classifier against the two hundred and seventy-one rows I had already named by hand. It agreed with me thirty-eight per cent of the time, and the disagreements had a pattern:

got "10R 1W"       want "10R"
got "330R 125mW"   want "330R"
got "22K 1/10W"    want "22K"
got "1k 500mW"     want "1K"

Forty-nine of my sixty-two resistor rows are named by value alone. I had stated a convention that my own data contradicted four times out of five. I was describing what I thought I did, not what I do.

That is the part I did not expect. The inventory was a labelled training set sitting there the whole time, and it corrected me rather than the other way round. The rule I gave in the end was the one the data implied: value with a rating where the rating is meaningful, value alone otherwise, and where nothing resolves, leave the field exactly as it is.

The rest of the day was cleanups run as dry runs first. A hundred and two package spellings folded to ninety-two, forty-three rows normalised. Twenty-six names shortened. Seven rows recovered a footprint that had been sitting in the description all along.

The reviews were the point

I asked for a table before each cleanup touched anything, and every table caught something.

Two rows, a red banana jack and a black one, both sitting in the same bin, would have been renamed SE503 and SE503. Identical names for parts I need to tell apart by colour. A voltage regulator SL1117-3.3 would have become SL1117, silently merging it with the 5V variant — the fix that came out of that also rescued three other rows I would never have checked.

And one question — ch224 package not found? — turned out to be a real bug. ESSOP-10 matched nothing at all, because a guard I had added to stop TSSOP-24 matching as SOP-24 also blocked every other prefixed variant. Seven rows recovered a package once it was fixed.

None of those were caught by tests. There are two hundred and forty-two tests and they all passed throughout. They were caught by a person who knows that a red jack and a black jack are different parts, reading a table.

The scarce half, again

I wrote about this in the stencil tool — that the domain knowledge did not become obsolete, it became the scarce half. Today made the same point from the other direction.

Claude wrote all sixteen hundred lines. I could not have written the tiered matcher or the specificity scoring. But every judgement that made the tool correct rather than merely working was mine: that stock counts are too unreliable to affect a match, that a mount style is not a footprint, that a blank field beats a confident wrong one, that two banana jacks in the same bin need different names.

The tool that reads my inventory had to be taught how I write. It turned out I did not know that myself until the data said so.

If you are building something that reads your own data, benchmark it against the rows you produced by hand before you write a line of the rules. You are probably not doing what you think you are doing.