A blank cell is four different problems
An empty field in your enrichment table can mean four incompatible things. Treat them as one and every number you report afterwards is wrong.
You export the table, open the CSV, and 8,000 rows have no email. The obvious read is that the email finder has a 40% hit rate and you need a better provider.
That read is usually wrong, because an empty cell is not one thing. It is at least four, and they call for opposite responses.
The four states
Never ran. The column was never triggered for that row. Nothing was attempted, nothing was spent, and nothing failed. These rows are not losses - they are work you have not done yet.
Ran and errored. The provider was called and returned an error: a timeout, a rate limit, a malformed payload. These are recoverable. Re-run them and most come back.
Ran and found nothing. A real negative. The provider looked and there is no email to find. This is the only state that tells you anything about hit rate.
Correctly skipped. A run condition excluded the row on purpose - no company domain, no first name, already contacted. The system worked exactly as designed.
Four states. One appearance. If you compute a hit rate by counting empty cells, you have added never-ran and correctly-skipped rows into your denominator, and your provider now looks far worse than it is.
Why the export cannot tell you
Here is the part that catches people: an export contains values, not run status. The CSV has no idea whether a cell is empty because nothing ran or because something ran and came back with nothing. That information exists in the table’s per-column status, and it does not survive the download.
So a funnel built from an export is a funnel built on a guess. It will look rigorous - the row counts are real, the percentages add up - and it will be wrong, because the shape it is measuring is not the shape you think.
I have made this mistake and shipped the number before catching it. The correction was not small. What looked like a step where half the batch died turned out to be a step that had processed half the batch so far.
What to do instead
Read the run status, not the value. Every enrichment column reports how many rows succeeded, failed, are still queued, and were excluded by their condition. That is the primitive. The value is downstream of it.
Name your denominator out loud. Not “40% had no email” but “of the rows that were actually processed, X% returned no email, Y% errored, and Z% are still queued.” If you cannot say which population you divided by, you do not have a number yet.
Separate in-progress from terminally failed. A row waiting in a queue and a row that has exhausted every provider look identical in a spreadsheet and mean completely different things for what to do next.
Re-run the errors before judging the provider. Errors are the cheapest rows to recover and the most likely to be misread as negatives.
The general shape
This is not really about enrichment tables. It is about a class of bug where absence looks uniform and is not.
A missing reply might be a prospect who is not interested, or a message that landed in spam, or a message that never sent. A missing lead might be a disqualified company or one that has not been enriched yet. In each case the observable is the same - nothing there - and the correct action is different.
Any check that starts from what is present cannot see what is absent. It will report green while missing the largest failure class there is. The fix is to anchor at least one check to something upstream and independent, so that two numbers derived different ways have to agree.
When they disagree by one row, you have found a real bug. When you only ever compare a file to itself, you will never find anything.