In a batch product-intake screen, the success count is easy to notice first. Unless the program defines what counted as success, that number says little about completed work. After reproducing four counterexamples in an existing CSV path, I separated failures to parse the file from invalid values in an individual record.

I started with what “one success” actually counted

I read the existing CSV path and exercised its actual handler with registration-callback and state-display stubs. I tried an unclosed quote, an extra column, a price containing letters and a negative price separately. All four called the registration callback once and counted one success. This reproduced input-processing conditions; it did not register real products with an external service.

The text 12oops34 became 1234. Removing nonnumeric characters can look convenient while manufacturing an amount different from the supplied value. A negative number is also truthy, so checking only whether a value exists did not reject it. I decided to separate the convenience of reading data from the decision to change what that data means.

A file-wide syntax error stops before the first row is created

An unclosed quote changes how later commas and line breaks can belong to a cell. That differs from a bad price in one otherwise identifiable record. Even if an earlier record looks valid, the file must first be split consistently. The repaired path parses the complete CSV text before entering the registration loop.

One actual regression input put a valid record before a later unclosed quote. The expected result was no callback at all, rather than creating the early record first. Unexpected text after a closing quote and an unescaped quote in a plain cell were rejected as syntax failures too. Defining partial success begins with identifying which boundary failed.

Separately count row errors once record boundaries are known

After the file has been split correctly, each record still needs semantic validation. The native screen expects a five-column header and data. I replaced the behavior that silently took the first five cells and discarded extras with explicit header and record-width checks. Short or wide data records remain in the failure list, while unrelated valid records follow the existing sequential processing path.

This does not implement an atomic whole-file commit. A valid record can fail at the real registration stage, and a later callback can throw after an earlier record was processed. The repair prevents creation before syntax validation and distinguishes row-level input failures. Retries or all-or-nothing rollback need their own confirmed-result, duplicate-request and storage boundaries.

Accept declared numeric forms instead of repairing prices

I restricted price input to positive finite numbers. Plain numbers, decimals and correctly grouped thousands separators are accepted; letters, units, currency symbols and signs are not stripped to obtain another value. An unsupported notation stays a correction task for the operator. This validation infers neither the source currency nor a real exchange rate.

Valid inputs also had to survive the repair. I checked quoted commas, embedded line breaks, doubled quotes, a leading BOM and CRLF, including the generated-template style. A validator that blocks bad input but cuts apart valid product names or options introduces a different defect. Success required both rejecting invalid cases and retaining the supported valid ones.

Keep the test target separate from operating data

In a separate development stage, nineteen tests exercised the native pure functions and the actual CSV handler. Registration callbacks and state displays were stubs. I also checked an isolated build and the existing twenty-six mock API checks with separate data and a separate port. I did not add those numbers together as proof of real supplier access, mail delivery or order placement.

I compared bytes and modification times for 111 protected files, including operating data and the original build. Eleven before-and-after source files were actually extracted from backup, and the restored code passed the same nineteen CSV tests. The original operating build was retained and the test build stayed isolated. This content task reads those actual records; it does not rerun the native product tests.

The shared example produces a review plan rather than importing

The existing import-review module handles duplicates, missing fields and revision conflicts in already parsed rows. The product-candidate module separates values retrieved from a source from values supplied by an operator. Neither is a raw CSV parser. I authored a separate synthetic example limited to syntax preflight and row review, with links to those two existing responsibilities.

The new example uses three columns without real product addresses or business records. Its explicit two-decimal integer amount contract and size limits are narrower than the native app’s numeric contract; it is not an installed replacement adapter. Even valid candidates leave the actual imported count at zero. Reading a result viewer differs from executing a parser, just as producing a candidate differs from completing a save.