DEV Community

Chris Sellers
Chris Sellers

Posted on

A traceability import should never guess a missing lot code

A supplier sends a CSV of shipping rows. Row 37 has no traceability lot code. The importer has three choices: reject the row, store it with a warning, or fill the gap with something like UNKNOWN so the batch goes through. The third option feels friendliest on upload day and does the most damage during a trace request.

This post is about where to draw that line for FSMA 204 records, the FDA food traceability rule. The pattern applies to any import where a missing field can quietly turn into a wrong answer later.

What a shipping record has to carry

For foods on the Food Traceability List, the rule names key data elements (KDEs) for each critical tracking event (CTE). For shipping, 21 CFR 1.1340(a) lists eight:

  1. Traceability lot code
  2. Quantity and unit of measure
  3. Product description
  4. Location description for the immediate subsequent recipient
  5. Location description for the location you shipped from
  6. Date shipped
  7. Location description for the traceability lot code source, or a traceability lot code source reference
  8. Reference document type and number (a bill of lading, for example)

Receiving under 1.1345(a) mirrors this, with the immediate previous source, the receiving location and the receive date in place of the shipping fields.

Read that list as a data contract. Every field is either there or it is not. The interesting design question is what your system does with the rows where it is not.

Three ways to handle a missing field

Reject. The row never enters the system. The uploader gets an error naming the row and the field. Nothing downstream ever sees a half record.

Store and flag. The row goes in, marked incomplete, with a warning that names the missing element and the section it comes from. Someone can fix it later, and every report that touches it knows it is incomplete.

Fill and continue. The importer writes a placeholder (TLC-UNKNOWN, Unnamed Facility, today's date) and logs a warning nobody reads. The record now looks complete.

Fill and continue is the trap. The placeholder is a value, so every later check that asks "is this field present?" says yes. Worse, placeholders collide. If ten suppliers each upload a row with no lot code and all ten become TLC-UNKNOWN, a trace on that "lot" links ten unrelated shipments. During a recall you have just built a false connection, or hidden a real one.

Where to draw the line

The split I have landed on:

  1. Reject when the row cannot identify what moved. No lot code, no product, no quantity or unit, no date. Without those, the row cannot be joined to anything, so storing it only creates noise. Fail loudly at upload, while the person holding the paperwork is still looking at it.
  2. Store and flag when the row identifies the lot but is missing context. A missing reference document number or location description is a real gap, but the event still happened and the lot is still traceable through it. Keep the row, mark it, cite the section, and make the gap show up in every export.
  3. Never fill. If a default seems tempting, that is usually a sign the field belongs in the reject group or the flag group, not in a third group of invented data.

The flag has to travel. A warning that lives only in an upload log is gone the next day. Put the incomplete state on the record itself so a sortable spreadsheet, an export and a trace query all carry it.

Present is not the same as correct

Even a perfect completeness check only proves a value exists. It does not prove the lot code matches the physical label, that the quantity is right, or that the ship to location is the real recipient. Those need a cross check: the receiving record at the next node should name the same lot code your shipping record did, and the quantities should reconcile within whatever tolerance the business accepts.

So I think of it as two layers:

  • Completeness: does each required element have a value? Cheap, deterministic, run on every row.
  • Consistency: do the values agree with the records on either side of the event? Harder, needs both trading partners' data, and is where most real traceability failures hide.

Plenty of systems ship layer one and call it done. It is a good start. It is not the same promise.

A few small things help at the completeness layer: normalize units (cs, case, CS) before checking, warn when a lot code mixes the letter O with zero or the letter I with one, and cap upload size so one bad file cannot bury a reviewer in thousands of warnings.

How I am building this in RegEngine

I'm building RegEngine, an FSMA 204 evidence layer. Today it takes CSV in its own column layout for shipping and receiving events and checks each row against the required KDE lists from 1.1340(a) and 1.1345(a). A row missing the lot code, quantity, unit, date or product is rejected rather than stored. Other missing elements come back as warnings that cite the CFR section.

What it does not do yet:

  • No automatic bill of lading reading. No PDF, scan, API, EDI or EPCIS intake. If you have a paper BOL, someone types it into the CSV.
  • Completeness, not correctness. The checks confirm a value is present. They do not prove it is right.
  • Not a legal determination. A clean check does not mean a record satisfies the rule or any buyer's program. Requirements vary by buyer, so confirm with your retailer.

It is pre-revenue, and I am biased. The FDA compliance date for the rule is July 20, 2028, but some buyers ask sooner, which is why import rules are worth getting right early.

Disclosure

I'm Chris, the founder of RegEngine. This post was drafted with help from an AI assistant, and the RegEngine behavior above was checked against a review of its code. More at regengine.co.

My question for you: when a supplier file arrives with a missing lot code, does your pipeline reject the row, store it flagged, or fill a placeholder so the batch goes through? I would especially like to hear from anyone who has had to untangle placeholder lot codes after the fact.

Top comments (0)