Skip to content

Extracting Batch and Lot Numbers From a Manufacturing Traveler

9 min read · updated August 11, 2026

A traveler looks like it has one lot number in the header. It usually has five or six, they are not all the same, and the differences between them are the record. Extract the header and you have the answer to the question nobody asks.

What a traveler is and why it has many lot fields

A manufacturing traveler — also called a router, a shop traveler, a job packet or, in a pharmaceutical plant, part of the batch production record — is the sheet that physically accompanies material through production. It lists the operations in order, and each operation has a row that an operator signs and dates when it is complete, often with an inspector’s second signature and a machine or work centre identifier.

Lot identifiers appear at several levels on that sheet, and they mean different things:

  • The work order or batch number in the header, which identifies this production run.
  • The product lot assigned to the output, which is what ends up printed on the finished goods.
  • Component or raw material lots consumed at specific operations, recorded per operation. These are the incoming lots that link back to a supplier’s certificate of analysis or mill certificate.
  • Sub-lot identifiers created mid-run when the batch is divided.

In the US pharmaceutical context the reason these are on the paper at all is 21 CFR 210.3, which defines a lot number or control number as a distinctive combination of letters, numbers or symbols from which the complete history of the manufacture, processing, packing, holding and distribution of a batch can be determined. The regulation defines the identifier by what it must let you reconstruct. That is a useful thing to hold in mind while deciding what your extraction may throw away: anything you drop is a step of a history somebody may be required to reconstruct.

Parent lots, sub-lots and splits

A batch splits for ordinary reasons. Half goes to one oven because the other is down. A quantity is pulled for destructive testing. A quarantined portion is reworked and rejoins the run three operations later. Manufacturers handle this with a suffix convention on the parent lot, and the convention is house style rather than a standard: a synthetic example would be parent A26-0917 splitting into A26-0917-1 and A26-0917-2, or into A26-0917A and A26-0917B, or into a fresh identifier with the parent recorded in a “derived from” field.

The extraction consequence is direct. If you pull one lot_number per document, a split traveler gives you whichever identifier the model liked best, and the relationship — this material came from that material — is gone. A recall walks that relationship in both directions: forward from a suspect raw-material lot to every finished lot that consumed it, and backward from a customer complaint to the components. A flat field cannot answer either query.

So extract lots as edges, not as values. Every lot mention gets a record with the operation number it appeared at, the role it played (consumed, produced, split from, reworked into), and the raw string as printed. Derive the tree afterwards from the roles, in code you can test, rather than asking the model to infer parentage from the layout. Asking a model “which lot is the parent” is asking it to apply a numbering convention it has not been told; asking it “list every lot-like token with its operation row and the label nearest to it” is asking it to read.

One more split case that catches people: a rework loop means the same operation number appears twice on the traveler with different dates. Key operation rows by their position in the document as well as by the printed operation number, or the second pass overwrites the first.

There is no check digit, so check against a register

Several identifiers elsewhere in document extraction can be validated by arithmetic — an ISBN-13 and a GTIN carry a mod-10 check digit, an IBAN carries mod-97. Lot numbers carry nothing. In the GS1 system a batch or lot is application identifier 10, a variable-length alphanumeric field, and it has no check character at all; the check digit that exists in a GS1-128 barcode belongs to the GTIN in AI 01 and says nothing about the lot in AI 10.

That absence changes the design. Where a checksum exists you can accept an extraction on arithmetic alone. Where it does not, the only validation available is membership: does this string exist in the lot register, the ERP, or the list of lots issued for this product? Build that lookup into the pipeline and treat a lot that resolves as high confidence regardless of what the model reported, and a lot that does not resolve as a review item regardless of how confident the model was. Character-level confidence is a poor proxy for correctness on exactly the characters that go wrong here: 0 and O, 1 and I and l, 5 and S, 8 and B.

When a candidate fails the lookup, a bounded confusion-set search is worth running before sending it to a human: generate the variants produced by substituting each confusable character and test each against the register. If exactly one variant resolves, you have a strong repair; if two do, you have an ambiguity a person must settle, and it is much better to know that than to pick.

The stamped and handwritten layer

Travelers are printed and then written on. The form, the operation list and often a barcoded work order are typeset; the lots consumed, the operator initials, the dates, the quantities and any deviation note are handwritten or applied with an inspection stamp. That mixture is the reason document-level confidence on a traveler is close to meaningless — the typeset half will be recognised almost perfectly and drag the average up over precisely the fields you care about. The general treatment of that problem is on the mixed handwriting and print page; the traveler-specific part is that inspection stamps are not handwriting and not print. A stamp is a fixed glyph — an inspector number inside a shape — and it is better detected as a mark in a region than transcribed as text.

Deviation and nonconformance notes deserve their own field rather than a general comments blob. They are the rows a quality investigation reads first, they frequently reference a separate document number (“see NCR 2026-0412”), and that reference is a link you want in your data.

A shape that survives a recall query

{
  "document_id": "TRV-2026-004411",
  "work_order": "WO-88213",
  "product": { "part_number": "PN-4471-C", "revision": "D" },
  "lots": [
    { "value": "A26-0917",   "role": "produced",   "operation": null, "raw": "LOT A26-0917" },
    { "value": "A26-0917-1", "role": "split_from", "parent": "A26-0917", "operation": "040" },
    { "value": "RM-77120",   "role": "consumed",   "operation": "010",
      "material": "6061-T6 bar", "source_doc": "MTR-77120" }
  ],
  "operations": [
    { "number": "010", "description": "Saw to length", "completed": "2026-03-04",
      "operator_initials": "R.M.", "inspection_stamp_present": true }
  ],
  "deviations": [
    { "operation": "040", "reference": "NCR-2026-0412", "text_present": true }
  ]
}

The two design choices there are worth stating plainly. Lots are a list with roles, so a split or a rework adds an element rather than overwriting a field. And every lot keeps its raw string alongside the normalised value, because when the register lookup fails, the raw string is the only evidence of what was actually printed — and the normalisation you applied is a far more likely culprit than the operator. Keeping both is the same discipline that audit logging asks for elsewhere, and on a document whose entire purpose is reconstructable history it is not optional.