Packaging and Serialization: Vision, Track-and-Trace, and Anomalies
📍 Where we are: Part V · Fill-Finish & Release, Learned — Chapter 19. The previous two chapters released the drug product: formulation and fill-finish put it in vials under camera inspection, and QC and release signed off the lot. Now those vials become saleable units — labelled, serialized, packed, and reported to a national track-and-trace system — and machine learning shows up in two very different guises: seeing the print on a vial, and reasoning over the stream of serial-number events the packaging line emits.
The drug product is made and released. What stands between it and a patient is the least glamorous, most legally exacting stretch of the whole process: packaging. Each DP-001 vial — DP-001 is the running example's drug-product code, and these vials come from the fill of the batch labelled BATCH-2026-001 — gets a printed label, a lot number, an expiry date, and — this is the part that makes packaging a data problem and not just a mechanical one — a unique serial number encoded in a 2D DataMatrix, commissioned (formally registered as existing the moment the unit is accepted) and tracked from the moment of fill to the moment of dispense. Two regulatory regimes force this: the U.S. Drug Supply Chain Security Act (DSCSA) and the EU Falsified Medicines Directive (FMD). Both demand that every saleable unit be uniquely identified, that its movements be reportable, and that the supply chain be able to verify a unit is genuine before it reaches a patient [1][2].
Machine learning enters this world from two sides. On the physical line, computer vision reads the print, checks the label, and decodes the 2D code on every unit at hundreds of units per minute — the same deep-learning vision family that inspected the filled vial in Chapter 17, now pointed at text and codes instead of particulates. On the data side, the line emits a torrent of serialization events — commissioning, aggregation, decommissioning — and an anomaly-detection layer watches that stream for the structural signatures of error and fraud: a duplicate serial, an uncommissioned unit, a case whose contents do not match its declared count. This chapter learns both halves, and ties them to the SGTIN-serialized vials of the running example: 480 vials of BATCH-2026-001, two of them rejected at the line, the rest commissioned and packed.
Think of an airport. There are two kinds of checks. One is the camera at the gate that reads your boarding pass and matches your face — fast, physical, per-person; if the print is smudged or the photo does not match, you are pulled aside. The other is the system behind the scenes that knows your ticket should not also be in use in another city, that a flight's manifest must match the number of people who boarded, that timestamps must run forward. Packaging ML is exactly these two. The vision system is the camera reading the label and the 2D code on every vial. The track-and-trace anomaly layer is the back-office logic that catches a serial appearing twice, a case packed short, a unit reported as shipped before it was ever made. One looks; the other reasons.
What this chapter covers
- Computer vision for print and label verification: OCR for lot/expiry text, DataMatrix decoding, and print-quality grading — a per-unit image classifier and reader at line speed.
- Serialization and the SGTIN: how a vial becomes a globally unique serialized identifier, and why that identifier is the join key for the entire downstream supply chain.
- Aggregation integrity: parent-child packing relationships (vial → bundle → case → pallet) and the deterministic rules that keep them honest.
- Anomaly detection over DSCSA/EPCIS event streams: a two-layer design — deterministic GS1 rules first, unsupervised ML for the residual of weird-but-legal cases, with the Isolation-Forest scoring logic spelled out in plain language.
- The anatomy of one serialization event, the GMP (Good Manufacturing Practice — the legally binding, regulator-inspected regime a medicine must be manufactured under) and Annex 22 angle, and the honest limits of what ML can and cannot decide here.
Two ML problems hide inside one packaging line
Packaging looks like one operation, but the machine learning splits cleanly into two problems with almost nothing in common except the vial they share.
The first is perception. A camera images each unit — the label, the printed lot and expiry, the 2D DataMatrix — and software must decide, in the few milliseconds before the next vial arrives, whether the print is legible, correct, and grade-passing, and what the code actually says. This is a vision and reading task: pixels in, a decision and a decoded string out. It is dominated by the same convolutional and, increasingly, transformer architectures (two families of neural network — the first scans the image with small learned pattern-filters, the second learns which parts of the input to attend to) that drive automated visual inspection (AVI), and it shares AVI's central tension — a rule-based reader that over-rejects good units versus a learned one that generalizes across print variation but must be validated against a defect library [3][4].
The second is integrity reasoning. Independent of how any single label looks, the serial numbers themselves form a graph of events — this serial was commissioned here, packed into that case, that case onto this pallet — and the question is whether the whole graph is internally consistent and consistent with reality. This is not a vision problem at all; it is anomaly detection over a structured event log. The two problems demand different models, different data, and different validation evidence, and conflating them is the first mistake. The rest of the chapter takes them in turn.
A second structural difference matters for validation. The perception model is per-unit and stateless — it judges one image against a learned class boundary and forgets it. The integrity model is relational and cumulative — its verdict on a case depends on every serial commissioned before it and on the count it was declared to hold. That difference is why the perception model is validated like an instrument (sensitivity, specificity, a defect-library challenge set) and the integrity model is validated like a process control (does it flag the seeded violation, and only it). They sit on the same line; they are not the same kind of software.
Vision at the line: OCR, DataMatrix, and print grading
The packaging camera does three jobs on every unit, and each maps to a distinct ML task. Understand them as three different stacks that happen to share an image sensor.
Optical character recognition (OCR) reads the human-readable text — the lot number, the expiry date, the product name — and checks it against what the batch record says should be printed. Modern pharmaceutical OCR is a learned sequence model. The canonical architecture is CRNN (Convolutional Recurrent Neural Network) — three stacked neural-network stages. First, a convolutional feature extractor (a small CNN, or convolutional neural network — the same image-pattern layer used in the vial inspector) turns the cropped text strip into a left-to-right sequence of column-wise feature vectors. Next, a bidirectional recurrent layer — an LSTM or GRU (memory-cell sequence networks that carry context as they scan) — reads that sequence both left-to-right and right-to-left, so each character is read in the context of its neighbours. Finally, a CTC (Connectionist Temporal Classification) decoder aligns the variable-length character sequence to the fixed grid of feature columns without needing per-character bounding boxes (a marked rectangle drawn around each character before reading it).
The problem CTC solves is an alignment one: the network produces one prediction per image column (say 40 columns) but the text has a different, variable number of characters (say 7), and no one has marked which column belongs to which character — and the alternative, drawing a box around each character first, is brittle. CTC handles this automatically. It works by summing the probability over every column-to-character alignment that collapses (after merging repeats and removing a blank symbol) to the target string; training maximizes the log-likelihood of the correct string under that sum, so the network learns alignment as a by-product of learning the characters. The newer pattern replaces the RNN-plus-CTC tail with a small vision transformer that attends over the strip and emits characters autoregressively, which reads curved or unevenly-spaced print more robustly.
The pharma-specific difficulty is not reading clean text — that is essentially solved — it is the failure modes: a half-printed character from a fading ribbon, a smear, a double-print, a correct date in the wrong format. The model must not merely emit the most likely string; it must expose a per-character confidence (for CTC, the model's own estimated probability that the chosen symbol at each step is correct) so that a low-confidence read is flagged rather than silently trusted. A confidently-misread expiry date is far more dangerous than an obviously unreadable one, so the operating point is tuned to reject into the doubt band, not to maximize raw read accuracy.
DataMatrix decoding reads the 2D code that carries the GS1 (the global supply-chain standards organization that defines the GTIN, SGTIN, and EPCIS used throughout this chapter) application identifiers — the GTIN (Application Identifier 01, written AI 01 — note this "AI" means GS1 application identifier, not artificial intelligence), the serial (AI 21), the lot (AI 10), and the expiry (AI 17), all in one square of black-and-white modules (the small printed cells, each one black or white, that make up the code). Decoding a clean DataMatrix is a solved, deterministic algorithm: locate the L-shaped finder pattern and the dashed clock track, sample the module grid, then run Reed-Solomon error correction, which is built into the ECC 200 symbology (the modern DataMatrix encoding standard) and can reconstruct the payload even when a meaningful fraction of modules is damaged. There is no ML in the decode itself, and there should not be — a probabilistic decoder of a legally-binding serial would be a liability. Where vision earns its keep is print-quality grading: scoring the printed code against the ISO/IEC 15415 parameters — each measuring one way the print can degrade, such as symbol contrast (black-vs-white separation), modulation (how cleanly each module reads as black or white), fixed-pattern damage (wear to the finder and clock pattern the decoder relies on), axial and grid non-uniformity (the cells stretched or skewed off the square grid), and unused error correction (how much of Reed-Solomon's repair margin is still spare) — and grading it on a school-style scale from A (best, scored 4.0) down to F (fail, 0.0). The point of grading is prediction: a code that decodes today on a pristine, well-lit verifier may fail months later on a pharmacist's cheaper handheld scanner under fluorescent light, and the unused-error-correction parameter is a direct measure of how much margin is left before that happens. A learned grader predicts decode-robustness from the printed image, catching marginal print before it ships rather than after a verification failure surfaces in the field. For codes marked directly on a curved or reflective vial rather than a flat label, the relevant grade is often reported under ISO/IEC TR 29158 (the direct-part-mark guideline) as well, which fixes the illumination angle so the grade is reproducible across verifiers — the standards-level expression of the very reader-dependence this section is warning about.
Label and print verification is the classifier proper: is the right label on the right product, applied straight, with no wrinkle, tear, bubble, or wrong-language insert? This is the closest cousin to AVI — a per-unit image classifier, typically a convolutional backbone (ResNet-class) with a small head over the inspected region — and it inherits AVI's economics. Rule-based label inspection is notorious for over-rejection — false rejects, meaning good units thrown out by mistake: rule-based AVI is widely cited (a conference/expert estimate, attributed to Amgen) as rejecting up to roughly 20% of good units (conference-reported, not peer-reviewed), and deep-learning vision is sold specifically as the fix for that false-reject burden [3][4]. The training discipline is the same one Chapter 17 insists on for AVI: a curated, versioned defect library as the challenge set, recall measured against seeded known defects rather than against an open-ended "looks fine" baseline, and the model locked at validation (frozen weights, eval() only) so that nothing learns in production. A label classifier that quietly updates itself is, under GMP, an unvalidated change.
The strongest production-grade anchor for learned vision in fill-finish and packaging is deep-learning inspection of vials and syringes. Stevanato Group's Vision AI platform claims up to 99.9% detection accuracy and roughly ten-fold fewer false rejects; Syntegon's AIM reports around 70% more particle detection and about 60% fewer false rejects; Brevetti CEA / Brevetti AI, Antares Vision, Cognex, and Körber sell comparable deep-learning inspection (production maturity). Every headline number here is vendor / self-reported (evidence tier: vendor materials, not peer-reviewed) — the 99.9% and 10× are vendor materials and the roughly 20% false-reject baseline is a conference expert estimate — and the one fully validated Amgen/Syntegon retrofit on record was a syringe line, not vials [3][4]. Treat the capability as real and the specific percentages as marketing. DataMatrix decode (Reed-Solomon) and ISO/IEC 15415 grading are, by contrast, standardized, production practice on every serialized line and carry no ML-credibility caveat at all.
Serialization: how a vial becomes an SGTIN
Before any of the integrity reasoning makes sense, follow one vial as it acquires its identity. The running example fills 480 vials of BATCH-2026-001, and the dataset records each as a serial like 00361414000017.0000153. That string is a SGTIN — a Serialized Global Trade Item Number — in two parts:
-
00361414000017is the GTIN-14: a globally registered identifier for the productmAb-Ain this presentation (the same number on every vial of this product everywhere in the world).1 It is carried in the DataMatrix under GS1 application identifier AI 01. -
0000153is the serial: unique to this one vial, carried under AI 21, so the GTIN-plus-serial pair identifies this individual unit and no other.
The DataMatrix on the vial carries this SGTIN together with the lot (BATCH-2026-001, AI 10) and expiry (AI 17), encoded with the GS1 application identifiers above. The moment the vial passes its in-process checkweigh, its SGTIN is commissioned: the line declares "this serial now exists, attached to this real unit." In the running example the IPC checkweigh stands in for the commissioning trigger, collapsing two stations the demo treats as one: on a real line the serialized DataMatrix is printed and commissioned at a print-and-apply station on the packaging line, often well downstream of the aseptic fill and its checkweigh — but the integrity logic is identical wherever the commissioning act sits. That single act is the seed of the entire track-and-trace record — and it is also the first integrity rule, because a serial that was commissioned for a unit the line rejected is, by definition, a phantom that should never have been born. In the running example two vials — serials 0000152 and 0000342 — were rejected at the line for low_fill and so are never commissioned; 478 of the 480 become real, packable SGTINs. Those two missing serials leave a one-unit gap in the otherwise contiguous serial range, and that gap is a small, real, structural signal the anomaly layer will later see.
The SGTIN is the join key for everything downstream. It is what a wholesaler scans on receipt, what a pharmacy verifies before dispensing, and — crucially for this chapter — the identifier around which the supply chain's event log is organized. In Book 4's ontology the vial is a node identified by a global IRI (a web-unique identifier for that node), and its SGTIN is the GS1 item key recorded against that node — two legitimate identity systems for one vial; here the SGTIN anchors a stream of events that an ML layer must keep honest.
The event log is a knowledge graph, and the rules are SHACL shapes
The reason the integrity reasoning in this chapter is grounded and not just a pile of if statements is that the same SGTIN that joins the supply chain is, in Book 4's terms, an IRI — so the append-only event log is literally a knowledge graph of typed RDF triples, not a CSV that happens to have a serial column. A commissioning event becomes bp:DP-001-0000153 bp:commissionedAt "…"^^xsd:dateTime; a packing event becomes bp:CASE-003 bp:contains bp:DP-001-0000153. Once the events are triples, two distinctions the rest of this chapter leans on stop being conventions and become modeled facts. First, the BFO split between a continuant (the vial, which persists through time) and an occurrent (the commissioning event, which happens at an instant) keeps the unit and the act of registering it as separate nodes — so "the vial" and "when it was commissioned" can never be silently conflated the way a flat row conflates them. Second, the lineage edge bp:derivedFrom (the vial came from DP-001, which came from the drug-substance lot) and the packing edge bp:contains (the vial is in CASE-003 right now) are kept deliberately distinct: containment is mutable — repack the case and it changes — whereas lineage is permanent, which is exactly why a recall scoped by bp:derivedFrom must never accidentally walk a bp:contains edge into a shipping carton.
Modeled this way, the deterministic rule layer is not ad-hoc code — it is the serialization analogue of Book 4's release gate bp:ReleaseShape. The same closed-world idea applies: a bp:CaseShape can require sh:minCount 48 and sh:maxCount 48 children per case (the exact-count invariant), sh:minCount 1 commissioning event per packed child (no uncommissioned child), and sh:maxCount 1 occurrence of any serial within a GTIN (no duplicate). SHACL (the Shapes Constraint Language — closed-world validation that a graph has the structure it must) treats a missing required commissioning event as a failure now, exactly as the release gate treats a missing sterility result — whereas an OWL reasoner, working open-world, would shrug it off as merely "unknown." That is the deep reason the rules carry the critical decision and the ML only advises: a SHACL shape is auditable, explainable, and complete-by-construction in a way a learned score is not, which is precisely what the draft Annex 22 demands of critical-decision logic. And the aggregation walk that lets a pallet scan imply every vial beneath it is one SPARQL (the standard query language for RDF graphs) property path — bp:contains+ (the + means "follow this edge one-or-more hops") — the supply-chain "inference" of this chapter made literal as a graph traversal. The same grounding is what lets a GraphRAG assistant answer "what is in CASE-003?" by traversing and citing the verified bp:contains edges rather than hallucinating a plausible manifest: the ontology is the schema the model is held to.
Aggregation: the packing hierarchy and the rules that protect it
A pharmacy does not order one vial; it orders cases. So serialized units are aggregated up a hierarchy — vial → bundle → case → pallet — and each aggregation is itself a recorded relationship: these serials are the children of that case, which is itself a child of that pallet. This parent-child structure is what lets a warehouse scan one pallet code and infer every case and vial serial beneath it without opening a single box ("inference" in the supply-chain sense, not the ML sense).
Aggregation is where the integrity rules concentrate, because the relationships must be exact and they are easy to break mechanically: a vial that falls off the line between commissioning and packing, a case sealed one unit short, a re-work that moves a unit to a new case without updating its parent. These are the deterministic invariants a track-and-trace system enforces, and they are not machine learning — they are GS1 / EPCIS business rules. It is worth being precise about which carry legal force: unique identification, no duplicate serial, and decommission-at-dispense are mandated by DSCSA and the FMD, whereas full case-level aggregation is a trading-partner expectation and inference convenience rather than a federal requirement (the FDA explicitly declined to require aggregation). Because no regulation forces it, different partners capture aggregation with differing rigour — which is exactly why aggregation data quality varies so much across the chain.
- No uncommissioned child. Every serial packed into a case must have been commissioned first.
- No commissioned reject. A serial attached to a unit the line rejected must never appear as a child of a case.
- Exact aggregation count. A case declared to hold 48 vials must contain exactly 48 child serials.
- No duplicate serial. Within a GTIN, a serial appears exactly once; a second occurrence is cloning or re-entry.
- No orphan child. Every child must have exactly one parent; a unit in two cases is impossible.
- No timestamp inversion. A unit cannot be aggregated before it was commissioned, or shipped before it was packed.
The reason these rules come first — before any ML — is regulatory and practical at once. They are deterministic, auditable, and explainable; a reviewer can read each one and a regulator can see exactly why a unit was held. Under the draft EU/PIC/S (Pharmaceutical Inspection Co-operation Scheme — the body that coordinates GMP inspection standards across regulators) GMP Annex 22 (the draft regulatory guidance specifically on AI in GMP manufacturing), the critical-decision logic in a GMP system must be exactly this kind of static, deterministic rule — the annex permits only static, deterministic models for critical use and excludes dynamic, continuously-learning, probabilistic, and generative AI from those decisions [5]. Machine learning, in this chapter, is therefore confined to what the rules cannot encode: the residual of cases that break no rule but are nonetheless statistically strange.
EPCIS event streams and where anomaly detection lives
Every one of those acts — commissioning, aggregation, decommissioning, shipping, receiving — is reported as an EPCIS event (Electronic Product Code Information Services, the GS1 standard for sharing supply-chain visibility data). An EPCIS event answers the questions what (the epcList of serials), when (the event time), where (two distinct fields: the readPoint where it was scanned and the bizLocation where it now resides), and why (the business step — commission, pack, ship — together with a disposition such as in_progress or in_transit). That disposition field is what a "shipped-before-packed" rule actually reads. A commissioning event is an ObjectEvent with business step commissioning; a packing event is an AggregationEvent that names a parentID and the child epcList. DSCSA and the EU hub both consume this event vocabulary, and the result is a high-volume, append-only log of structured events keyed by SGTIN [1][2].
Anomaly detection over this stream is the natural ML problem, and it has a very particular shape that dictates the model choice:
- It is overwhelmingly normal. The vast majority of events are legitimate, so there is no balanced labelled training set of "fraud" to do supervised classification on. Anomalies are rare, varied, and partly adversarial.
- It is weakly labelled at best. A confirmed fraud or recall is a rare, after-the-fact label; you cannot wait for thousands of them.
- It is structured and feature-rich. Each aggregation carries count, timing, serial-contiguity, and reject statistics that describe its "shape."
A fourth property is a governance one, and it is what separates a defensible anomaly layer from a misleading one: the stream is append-only and provenanced, with missingness that is itself a signal. Because EPCIS events are never edited in place — a correction is a new event, not an overwrite — the log is an ALCOA+-friendly record by construction: every event is attributable (the recordTime and the reporting party), contemporaneous (the eventTime), and enduring. That matters for the ML because a model fit on this stream inherits its provenance: each per-case feature row traces, by SGTIN, back to the very fill_events.csv rows the data-shadow of the line emitted, so a flagged case is explainable down to its source events rather than a verdict from a black box. It also reframes how missing data must be handled. A blank commissioning event is not a value to impute — under the closed-world rule layer it is a violation, the uncommissioned-child failure itself. So the integrity layer must keep a missing required event distinct from a present-but-zero one; treating "no commissioning row" as a quietly-imputed normal is exactly the data-governance failure the deterministic SHACL-style completeness check exists to prevent, and it is why the rule layer, not the Isolation Forest, owns the question of whether an event is absent.
This is the textbook regime for unsupervised anomaly detection — and specifically for tree-based isolation methods like the Isolation Forest, which scores a point by how few random splits it takes to isolate it, requiring no labels and handling the "rare-and-weird" definition of an anomaly directly. The same Isolation-Forest-plus-Random-Forest family appears in peer-reviewed CPV (Continued Process Verification — the ongoing monitoring of a validated process to confirm it stays in control) work elsewhere in biomanufacturing (Aizon and the Universitat Autonoma de Barcelona's "CPV of the Future," a proof-of-concept on a microbial model system — a simpler microbe-based process standing in for a full mAb run), which is why it is a defensible, well-understood choice here rather than an exotic one [6].
How the Isolation Forest scores a case
The method, written out as prose rather than formula, is the reason it fits this regime. An Isolation Forest builds many random binary trees (here 200). Each tree is grown by repeatedly picking a feature at random and a split value at random between that feature's min and max, partitioning the points, and recursing until every point sits alone in a leaf. The key observation is that a point that is different from the bulk gets isolated by a short chain of splits, while a point buried inside a dense cluster needs many splits to peel away from its neighbours. So the path length — the number of edges from the root to the leaf holding a point, averaged across all 200 trees — is short for anomalies and long for normal points.
To turn path length into a comparable score, the average path length is normalized by the path length you would expect for an average point in a tree of that size — a baseline that grows slowly (roughly with the logarithm of the sample size). Normalizing matters because in a bigger pile of cases even a normal point naturally needs a few more splits to isolate; without dividing out that size effect, larger datasets would make every point look more normal and scores from different-sized runs would not be comparable. The score is then defined so that it runs between 0 and 1: a point whose normalized path length equals the expected length scores about one-half; a point isolated far faster than expected pushes toward 1 and is flagged as an anomaly; a point that takes much longer than expected sits well below one-half and is deep normal. The example module reports the negated score_samples value (sklearn's score_samples returns higher numbers for more normal points, so flipping the sign makes higher mean more anomalous) — note this negated value rises and falls together with (is monotonic in) the canonical 0-to-1 score above, but is not literally equal to it, so read the printed column as a ranking and a flag, not as "fraction of the way to anomalous." A separate contamination parameter sets the fraction of points the model is told to expect as outliers, which fixes the threshold on that score above which flag becomes true. Three properties make this the right tool here: it needs no labels, it is near-linear in the number of points so it scales to a national event volume, and it scores "rare and structurally odd" directly rather than fitting a density it would then have to threshold.
The architecture that results is two-layered, and the layering is the design. The deterministic rule layer runs first and catches the bulk of real defects with full auditability; the unsupervised ML layer runs only on what survives, scoring cases that are legal but unlike the campaign — a case packed slightly short, a case whose commissioning span is oddly long, a case sitting at the edge of the serial range. The ML score is advisory: it raises a flag for a human to review, it does not autonomously hold or release a unit. That division is exactly what the FDA's 2023 Artificial Intelligence in Drug Manufacturing discussion paper and the draft Annex 22 both expect of ML touching a quality-relevant decision [5][7].
The two ML problems of packaging side by side: a per-unit vision reader (OCR, DataMatrix decode, label grade) on the physical line, and a two-layer integrity engine over the serialized EPCIS event stream — deterministic GS1 rules first, an unsupervised Isolation Forest for the residual, with the ML score advisory and the human deciding.
Original diagram by the authors, created with AI assistance.
A runnable model: serialization_anomaly.py
The example module examples/platform/ml/serialization_anomaly.py builds the whole integrity stack on the committed dataset examples/datasets/fill_events.csv. It commissions real SGTINs from the 480 filled vials of BATCH-2026-001 (keeping the leading zeros that pandas — the standard Python table library — would otherwise destroy: left to guess the column type it reads a serial like 00361414 as the number 361414 and the leading zeros vanish, so the code forces it to be read as text), excludes the two low_fill rejects so they are never commissioned, aggregates the survivors into cases of 48, runs the deterministic GS1 rule layer, and then scores the per-case feature table with an Isolation Forest. It runs standalone with no services.
The per-case feature table is the crux of the ML half, so read it field by field. Each case becomes one row with five features. n_vials is the child count. commission_span_s is the wall-clock seconds between the first and last commissioning timestamp in the case — a proxy for how smoothly the line ran while that case filled. mean_fill_g is the average checkweigh fill weight of the case's vials, tying packaging back to the fill-control checkweigh. fill_ratio is n_vials divided by the declared 48, so a full case is exactly 1.0 and a short case is below 1. serial_jump is the contiguity test: for a clean case the maximum serial minus the minimum serial should equal n_vials minus one (serials 1, 2, 3 give max − min = 2 for 3 vials, and 3 − 1 = 2), so any positive serial_jump means a gap inside the serial range — exactly the footprint the two rejected vials leave behind. The Isolation Forest scores rows in this five-dimensional space; everything it flags is a statement about case shape, never about a single label image.
mean_fill_g is worth dwelling on, because it is the one feature that reaches back into the bioprocess proper rather than the serial graph. On a real aseptic fill line the in-process checkweigh (the 100%-weighing station that gravimetrically checks each filled vial) is itself a validated unit operation governed by a fill-weight control chart: the target fill is the formulated dose plus a small deliberate overage, the action and warning limits are set from the line's qualified fill-weight standard deviation, and a vial outside the reject limits is the low_fill rejection that keeps two serials from ever being commissioned. Aggregating those per-vial weights to a per-case mean_fill_g makes the integrity layer a coarse process-capability sensor: a case whose mean drifts toward a control limit, or whose vial-to-vial spread widens, is a case filled while the pump or check-weigh was trending out of capability — the same Cpk thinking (process-capability index: how comfortably the spread of fills sits inside the spec window) that the fill-finish chapter applies vial-by-vial, now read one packing level up. The Isolation Forest never sees a single weight; it sees the case-level signature of a fill process that was in or out of control while that case was assembled, which is why a fill-weight excursion and a serialization anomaly can surface as the same flagged case.
# examples/platform/ml/serialization_anomaly.py (excerpt)
from pathlib import Path
import pandas as pd
from sklearn.ensemble import IsolationForest
DATA = Path(__file__).resolve().parents[3] / "examples" / "datasets"
VIALS_PER_CASE = 48 # vial -> case aggregation (illustrative hierarchy)
def commission_serials():
"""Each filled, NON-rejected vial is commissioned as an SGTIN at the fill line."""
fe = pd.read_csv(DATA / "fill_events.csv", parse_dates=["ts"],
dtype={"vial_serial": str}) # keep GTIN/serial leading zeros
fe["reject"] = fe["reject"].astype(str).str.strip().eq("True")
fe[["gtin", "serial"]] = fe["vial_serial"].str.split(".", expand=True)
commissioned = fe[~fe["reject"]].sort_values("ts").reset_index(drop=True)
return fe, commissioned
def aggregate_cases(commissioned):
"""Roll commissioned vials into cases of 48 and build the per-case feature table."""
c = commissioned.copy()
c["case_idx"] = c.index // VIALS_PER_CASE
g = c.groupby("case_idx")
cases = pd.DataFrame({
"case_id": ["CASE-%03d" % i for i in g.size().index],
"n_vials": g.size().to_numpy(),
"mean_fill_g": g["fill_weight_g"].mean().to_numpy(),
"min_serial": g["serial"].min().to_numpy(),
"max_serial": g["serial"].max().to_numpy(),
})
cases["fill_ratio"] = cases["n_vials"] / VIALS_PER_CASE
# serial contiguity: a clean case has max-min == n_vials-1 (no gaps/jumps)
cases["serial_jump"] = (cases["max_serial"].astype(int)
- cases["min_serial"].astype(int)
- (cases["n_vials"] - 1))
return cases
def rule_checks(fe, cases):
"""Deterministic GS1/EPCIS invariants — NOT machine learning, and audited first."""
findings = []
if fe.loc[~fe.reject, "vial_serial"].duplicated().any():
findings.append("duplicate_serial")
phantom = set(fe.loc[fe.reject, "vial_serial"]) & set(fe.loc[~fe.reject, "vial_serial"])
if phantom:
findings.append(f"commissioned_rejected_unit x{len(phantom)}")
short = cases[cases.n_vials != VIALS_PER_CASE]
if len(short):
findings.append(f"aggregation_count_mismatch x{len(short)} "
f"(e.g. {short.iloc[-1].case_id}={int(short.iloc[-1].n_vials)})")
return findings or ["none"]
def score_anomalies(cases, contamination=0.08):
"""IsolationForest over per-case features -> advisory score for legal-but-strange cases."""
feats = ["n_vials", "commission_span_s", "mean_fill_g", "fill_ratio", "serial_jump"]
iso = IsolationForest(n_estimators=200, contamination=contamination, random_state=2026)
X = cases[feats].to_numpy(float)
iso.fit(X)
cases = cases.copy()
cases["anomaly_score"] = -iso.score_samples(X) # higher = more anomalous
cases["flag"] = iso.predict(X) == -1 # -1 = outlier
return cases.sort_values("anomaly_score", ascending=False)
Running python platform/ml/serialization_anomaly.py prints the following block. Every number here is a verbatim run output over the real fill_events.csv — the GTIN, the 480/478 commissioned count, the two low_fill rejects, and the short final case — except the Isolation-Forest scores, which are deterministic given the seed but are an illustrative anomaly model over a clean campaign, not a learned fraud detector:
serialization / track-and-trace anomaly layer
GTIN-14 : 00361414000017
vials filled : 480
IPC rejects (not commissioned): 2 (['low_fill', 'low_fill'])
serials commissioned: 478
cases (48/case): 10 (last case is short: 46 vials)
-- rule layer (deterministic GS1/EPCIS invariants) --
n_commissioned: 478
n_rejected_not_commissioned: 2
rule_findings: ['aggregation_count_mismatch x1 (e.g. CASE-009=46)']
-- ML layer (IsolationForest, advisory) — top scored cases --
case_id n_vials fill_ratio serial_jump anomaly_score flag
CASE-009 46 0.958 0 0.735 True
CASE-007 48 1.000 1 0.582 False
CASE-003 48 1.000 1 0.576 False
CASE-001 48 1.000 0 0.529 False
CASE-000 48 1.000 0 0.437 False
NOTE: rule findings are deterministic and auditable; the IsolationForest score is ADVISORY only. Real vendor detection accuracy (e.g. Stevanato ~99.9% on the VISION half) is vendor/self-reported.
Read this output the way a serialization analyst would. The deterministic layer does the real work first: 478 of 480 vials are commissioned, the two low_fill rejects are correctly not commissioned (so they cannot become phantom units), and the one count that breaks the exact-48 invariant — the final case CASE-009 holding 46 vials instead of 48 because 478 does not divide evenly into cases of 48 — is caught by the exact, auditable aggregation_count_mismatch rule. (This short last case is an expected remainder, not a fault on the line; the rule still fires because its job is to report any off-count case so a human can confirm the reason — here, benign arithmetic.) Only then does the Isolation Forest run, and it independently surfaces the same CASE-009 as the top-scored, flagged outlier (anomaly_score 0.735, flag true) — agreeing with the rule but reaching it through statistics rather than logic, because the short case is the lone point with fill_ratio 0.958 instead of 1.000 and is isolated in just a few random splits.
The two cases with serial_jump of 1 (CASE-007, CASE-003) score slightly higher than their full, contiguous peers (0.582 and 0.576 versus 0.529 and 0.437) because the rejected serials 0000152 and 0000342 left a one-unit gap that broke serial contiguity inside those cases. This is precisely the kind of weak structural signal the ML layer exists to notice — and notably none of them crosses the contamination threshold into a flag. This is the whole design in miniature: rules catch the certain, ML scores the suspicious, and a human reads the flag.
One honest caveat the demo makes concrete: this forest is fit on the ten cases of a single golden run, and at N=10 it is closer to memorizing ten points than to learning a distribution — sklearn even draws every tree from all ten. The mechanics are real; the generalization is not, and would only come from training across hundreds of campaigns. That gap is the small-data ceiling showing up one last time, on the last problem in the factory.
Anatomy of one serialization event
A serialization event, like every artifact in this series, is not a bare serial number — it is a structured EPCIS record that ties the unit to what happened to it, where, when, and why, plus the model outputs and integrity verdicts that travel with it. Dissect one commissioning-then-packing event the way a track-and-trace reviewer would.
One serialization event, fully unpacked: the EPCIS what-when-where-why core, the vision reads (OCR text with confidence, decoded DataMatrix, print grade) that physically verified the unit, the aggregation parent it belongs to, the deterministic integrity verdicts that are the critical-decision logic, the advisory Isolation-Forest anomaly score that is not, and the lineage tying the event to DP-001, its case, and the national hubs it is reported to.
Original diagram by the authors, created with AI assistance.
Read the card top to bottom and the chapter is laid out as fields.
EPCIS core — the event proper. Four fields. What is the epcList, the SGTIN(s) the event is about (here 00361414000017.0000153). When is the event timestamp, recorded with timezone offset so cross-jurisdiction events stay orderable. Where is the read-point business location (FILL-A line). Why is the business step — commissioning for the ObjectEvent, then packing for the AggregationEvent. These four are the GS1-mandated minimum, and they are also exactly the columns the rule layer reads.
Vision block — what the camera physically verified. Three sub-fields, each from one of the three vision tasks above. The OCR-read lot (BATCH-2026-001) and expiry travel with a read-confidence, so a low-confidence read is flagged rather than silently trusted — the failure-mode discipline made into a field. The decoded DataMatrix payload is the deterministic Reed-Solomon decode of the 2D code (no confidence needed; it either decodes or it does not). The label/print grade carries its ISO/IEC 15415 letter grade, the predicted decode-robustness, not just a pass bit.
Aggregation block — the parent. parentID (CASE-003) and the declared 48-child count, making the parent-child relationship explicit and checkable. This is the field the aggregation_count_mismatch and orphan-child rules operate on.
Integrity block — the critical-decision logic. The deterministic rule verdicts: commissioned-before-pack (true), not-a-reject (true), count-exact (true), no-duplicate (true). Each is marked deterministic because these are the verdicts a regulator reads and the only ones permitted to gate a unit under Annex 22.
Advisory block — the part that cannot gate. The Isolation-Forest anomaly score for the parent case, marked advisory and illustrative, plus a human-review flag (false). The score lives on the card precisely so that a reviewer can see it was considered and that it did not make the call — the audit-trail expression of "ML scores, human decides," and the ALCOA+ data-integrity principle (records must be Attributable, Legible, Contemporaneous, Original, and Accurate — the ALCOA five — plus the four "+" attributes: Complete, Consistent, Enduring, and Available) made concrete: the advisory score is recorded contemporaneously and attributably so the decision stays reconstructable.
Relationships (violet) — lineage and containment. Here derivedFrom DP-001 is the lineage edge (where the unit came from), while contained-in CASE-003 is the separate packing-containment edge — Book 4 keeps these two hierarchies deliberately distinct (containment is mutable and is not lineage). The event is also reported-to the DSCSA and EU verification hubs and verified-at dispense — the last point in the running example's genealogy, where the SGTIN that began at fill is finally checked against the manufacturer's record before a patient receives the dose.
The unsolved part: rare, adversarial, and the saturation problem
Be honest about why serialization anomaly detection is harder than the clean demo suggests. The first difficulty is that the real anomalies are rare and adversarial. Unlike a drifting soft sensor, the most dangerous serialization anomalies — counterfeit cloning of a valid serial, diverted product (units pulled out of the legitimate channel, for example into the gray market) re-introduced into the legitimate chain — are produced by an adversary who is actively trying to look normal. An Isolation Forest trained on legitimate traffic learns what normal looks like, but a sophisticated counterfeiter studies the same distribution and packs their fakes to sit comfortably inside it. There is a structural reason, not just an empirical one: an unsupervised detector fits the distribution it observed, and a capable adversary optimizes against that same distribution, so the method offers no robustness guarantee at all. That is why "no flag" can never be read as "genuine," and why the adversary-independent deterministic rules — not the model — must carry the authenticity decision. The deterministic duplicate-serial rule, not the model, is what actually catches the canonical clone — which is the deeper reason rules carry the critical load.
The second difficulty is the alert-fatigue / saturation problem, the supply-chain cousin of AVI over-rejection. A track-and-trace system that flags too aggressively buries its real signals under a flood of benign oddities — a legitimately short final case (exactly the CASE-009 the demo flagged correctly but harmlessly), a re-work that updated parents correctly but unusually, a partner whose EPCIS clock is a few seconds off. Tune the Isolation Forest's contamination too high and human reviewers stop trusting the flags; tune it too low and the rare real fraud slips through. There is no clean operating point — the same recall-versus-precision tension that the release-prediction operating points make concrete — and the only durable answer is the two-layer split this chapter insists on: keep the certain, auditable rules carrying the load, and use the ML score as a prioritizer for human attention, never as a gate.
The third difficulty is fragmentation. DSCSA and the EU FMD are different regimes with different event models, different hubs, and different verification flows, and a single global product crosses both. An anomaly model trained on one jurisdiction's event shapes does not transfer cleanly to the other, and the most interesting anomalies — a unit that appears in two countries' systems at once — are precisely the ones no single jurisdiction's data can see. Cross-jurisdiction anomaly detection requires data sharing that competitive and privacy pressures make genuinely hard, which is why most deployed anomaly work today lives inside one manufacturer's serialization repository, not across the chain. This is also why the honest maturity label for learned serialization anomaly detection is pilot/research, not production: the method is sound and grounded in peer-reviewed CPV work, but the deployment surface is fragmented and the validation evidence thin [6].
What this chapter adds to the model suite
This chapter contributes examples/platform/ml/serialization_anomaly.py to the Book 5 example suite: a standalone module that commissions real SGTINs from fill_events.csv (correctly preserving the GTIN-14 and serial leading zeros), excludes the two low_fill IPC rejects from commissioning, aggregates the survivors into cases, runs a deterministic GS1/EPCIS rule layer (uncommissioned child, commissioned reject, aggregation-count mismatch, duplicate serial, timestamp inversion), and scores a per-case feature table with an unsupervised Isolation Forest. It coordinates with — and deliberately does not duplicate — the vision AVI sketch (vision_avi.py), which handles the perception half of packaging (the per-vial image classifier); this module handles the integrity-reasoning half (the serialized event stream). The commissioning counts, the GTIN, the two rejects, and the short final case are real committed-dataset facts; the Isolation-Forest scores are clearly labelled illustrative — they exercise a defensible anomaly model over a clean campaign rather than a learned fraud detector trained on confirmed fraud.
Why it matters
Packaging is the last place the digital thread touches the product, and it is where two regulatory regimes turn a manufacturing concern into a public-safety one. Get the vision right and the line auto-verifies print and codes at speed without burying operators in false rejects; get the integrity reasoning right and a counterfeit, a diversion, or a simple packing error is caught before it reaches a pharmacy shelf. Two kinds of software share that line, and they are validated two different ways: the per-unit reader you validate like an instrument and the relational integrity engine you validate like a process control. The discipline this chapter insists on — deterministic rules carrying the critical decisions, ML scoring only the residual and only as advice — is not a limitation to apologize for. It is exactly the architecture the regulators are converging on, and it is what lets a manufacturer deploy machine learning here at all without putting a probabilistic model in the path of a unit's release. The SGTIN that began on a vial of BATCH-2026-001 at fill is the same identifier a pharmacist scans before a patient receives the dose; learning to keep that identifier honest is the quiet finale of the manufacturing half of the process.
In the real world
The vision half of packaging is genuinely production: deep-learning inspection of labels, print, and codes is sold and deployed by Stevanato, Syntegon, Brevetti CEA, Antares Vision, Cognex, and Körber, and it is the strongest production ML story in the whole fill-finish-and-packaging area — with the standing caveat that every headline accuracy and false-reject number is vendor / self-reported and the one fully validated public retrofit was a syringe line [3][4]. DataMatrix decoding (Reed-Solomon) and print grading against ISO/IEC 15415 are mature, standardized practice on every serialized line, and they carry no ML caveat — they are deterministic and they either pass or they do not.
The track-and-trace anomaly half is more pilot than productized ML. DSCSA and EU FMD serialization themselves are fully production — every manufacturer commissions, aggregates, and reports EPCIS events today — but the deterministic rule layer does almost all the operational work, and learned anomaly detection over the event stream is an applied (pilot) idea that serialization-platform vendors are beginning to add rather than a settled, validated capability. The unsupervised-anomaly family this chapter uses (Isolation Forest, random forests) is the same one that appears in peer-reviewed biomanufacturing CPV work — Aizon and the Universitat Autonoma de Barcelona's "CPV of the Future" proof-of-concept — which grounds the method (evidence tier: peer-reviewed, self-authored) even though that work was on a microbial model system, not a serialization stream [6]. The broader frame is the one this whole book keeps returning to: the ISPE (International Society for Pharmaceutical Engineering — a pharma-engineering professional body) Pharma 4.0 (its digital-maturity framework for pharma manufacturing) reality is that production ML clusters in vision inspection and monitoring with a human in the loop, not in autonomous quality decisions, and a serialization anomaly score is squarely advisory under the FDA's 2023 Artificial Intelligence in Drug Manufacturing discussion paper and the draft Annex 22 [5][7]. And the cautionary tale sits one regime over: the April 2026 Purolea cGMP (current GMP — the GMP in force today) warning letter (an FDA enforcement notice issued when an inspection finds GMP violations) — the first to cite AI — flagged a firm using AI agents to generate specifications and master production records without quality-unit review, the exact opposite of the rules-first, ML-advisory discipline this chapter argues for [8].
Key terms
- Serialization — assigning a globally unique serial number to every saleable unit and tracking it through the supply chain, mandated by DSCSA (U.S.) and the FMD (EU).
- SGTIN (Serialized GTIN) — the unit identifier: a GTIN-14 (the product, AI 01) plus a serial (the individual unit, AI 21), e.g.
00361414000017.0000153; the join key for the whole track-and-trace record. - DataMatrix — the 2D barcode (ECC 200) symbology that carries the SGTIN, lot, and expiry on a vial; decoded deterministically with Reed-Solomon error correction.
- OCR (optical character recognition) — the learned vision task of reading the human-readable lot and expiry text (CRNN with a CTC decoder, or a vision transformer), where flagging low-confidence reads matters more than reading clean text.
- Print-quality grading — scoring a printed code against ISO/IEC 15415 parameters (contrast, modulation, fixed-pattern damage, unused error correction) and grading A–F to predict whether it will still decode downstream.
- Commissioning — the act of declaring a serial to exist, attached to a real, accepted unit; a serial commissioned for a rejected unit is a phantom.
- Aggregation — recording the parent-child packing relationships (vial → bundle → case → pallet) so a pallet scan implies the serials beneath it.
- EPCIS — the GS1 standard for sharing supply-chain events; each event records what, when, where, why for a set of serials (
ObjectEventfor commissioning,AggregationEventfor packing). - DSCSA / FMD — the U.S. and EU track-and-trace regulations driving serialization and verification.
- Isolation Forest — an unsupervised, label-free anomaly detector that scores a point by how short a chain of random splits isolates it (short path = anomalous); well suited to the rare, mostly-normal, weakly-labelled serialization regime.
- Two-layer integrity design — deterministic GS1/EPCIS rules for the auditable critical decisions, with an advisory ML anomaly score only for the residual of weird-but-legal cases.
- Alert saturation — the failure mode of an over-eager anomaly detector that buries real signals under benign flags, eroding reviewer trust.
- SHACL shape (serialization) — a closed-world constraint (e.g. a
bp:CaseShaperequiring exactly 48 commissioned children with no duplicate) that treats a missing required event as a failure now, making the deterministic rule layer the serialization analogue of Book 4's release gate. - Continuant vs occurrent (BFO) — the upper-ontology split that keeps the vial (a continuant that persists) distinct from the commissioning event (an occurrent that happens), so the event graph never conflates a unit with the act of registering it.
- Fill-weight control /
Cpk— the in-process-checkweigh control chart whose limits decide alow_fillrejection; aggregated tomean_fill_git makes the integrity layer a coarse process-capability sensor for the fill operation one packing level up.
Where this leads
The product is made, released, packed, and serialized; every vial of BATCH-2026-001 now carries an SGTIN that can be verified anywhere in the chain. What remains is getting it to the patient intact. The next chapter, Distribution: Cold-Chain Prediction and Demand Forecasting, leaves the factory entirely and learns the supply chain itself — predicting temperature excursions on a shipping lane before they happen, scoring route and partner risk, and forecasting demand so the right number of doses are made in the first place — closing the manufacturing spine where it finally meets the world.