Nine rows marked “skipped” that nobody skipped
- Symptom
On the first real workbook, a third of the first sheet came back skipped, including every basic company detail: organisation name, postal address, telephone, email, the name of the signing representative. The company profile holding all of those values was populated and correctly wired. Nobody had clicked skip all.
- Diagnosis
Those rows had never reached the answering step at all. The reader had classified them as section banners, meaning decoration, and rows classified as decoration are excluded from answering entirely, which the interface then surfaces as skipped.
The reason was the classifier’s very first gate: does this row have other cells filled in? If so, it is a real question. That gate is sound on the path it was written for, where the file being read is a completed questionnaire. On an unfilled returnable template every answer cell is blank by definition, so the gate can never fire, and short noun-phrase labels then fall through the remaining tests (under the length limit, under the word limit, no question mark, no requirement verb) and land in a weak structural test where a merged cell or a dense identifier column tips them to banner.
Worse, one signal was actively voting against them. The sheet’s merged value cell was being read as evidence for banner, because the merge-detection code kept each merge’s width and discarded its starting column. So a merge to the right of the question, which is what an answer cell looks like, was indistinguishable from a merge across the question itself, which is what a section header looks like.
And the safety net didn’t fire: a circuit breaker disables junk detection on a sheet when too many rows are flagged, but this was a third of them, under the threshold.
- Decision
Add signals, never replace the gate. That gate is live negative evidence on the shared questionnaire path, where it is right; the new signals are additive escape hatches that run after it. Three of them, and I checked which rows each one actually rescued before writing any:
a dropdown rule covering this row’s cell to the right of the question; a merge starting right of the question column, which needed the merge code to retain the starting column it was already computing and throwing away; and a vocabulary test for fill-in field labels.
The vocabulary test is the ugliest of the three and it stays, because the table of which signal rescues which row is unambiguous. The structural signals rescue the dropdown rows and the merged rows, and leave every unmerged free-text row still skipped: the address, the phone number, the representative. Those were the rows that started the investigation.
The test asserts the fix and asserts the circuit breaker did not fire, because a green test that passed via the safety net proves nothing about the signals.
The general shape: a heuristic that reads surrounding content is only as good as its assumption about what surrounds it. This one had been written against filled-in files and was correct there. Nothing was wrong with it except the input it now got, which is why it passed review, and why the bug appeared only on real customer templates.