Lot tracking¶
Lot tracking (batch identifiers, manufacture date, expiration date) is a v1.6 behavior with v1 schema commitments. The full schema and ingest fields are accepted from v1 launch — the upstream can register lots and reference them on movements — but FG.AI’s putaway and pick Rules do not yet evaluate lot-aware logic until v1.6 ships (Phase 14 per WMS ADR-0033).
Serial tracking (per-unit unique identifiers) stays v2 — the v1 schema accepts serial registrations but no v1.6 behavior is planned.
Per-SKU track_by mode¶
Lot vs. serial vs. neither is a per-SKU configuration in PSpec, expressed on the SKU master record:
POST /wms-ingest/v1/master/skus?mode=upsert
{
"sku": {
"source_id": "PHARMA-001",
"consignor_id": "pharma-co",
"track_by": "LOT", ◄── NONE (default) | LOT | SERIAL | LOT_AND_SERIAL
"lot_required_at_receive": true,
"expiry_required": true,
"min_shelf_life_days_at_receive": 90,
"min_shelf_life_days_at_ship": 30,
...
}
}
|
Inventory keyed by |
Activation |
|---|---|---|
|
|
v1 |
|
|
v1.6 (Phase 14) |
|
per-unit |
v2 |
|
serial within lot |
v2 (alongside SERIAL) |
track_by is immutable after the first downstream movement for the SKU (analogous to base-UoM immutability). Choose carefully at onboarding.
The validation flags (lot_required_at_receive, expiry_required, min_shelf_life_days_at_receive, min_shelf_life_days_at_ship) ship with v1 schema but only fire on receive/ship from v1.6. In v1, they are stored and surface as lot_validation_planned flags on the SKU readback.
Registering a lot¶
POST /wms-ingest/v1/master/lots?mode=upsert
{
"lot": {
"source_id": "LOT-2026-Q2-0042",
"consignor_id": "pharma-co",
"sku_ref": "PHARMA-001",
"lot_no": "LOT-2026-Q2-0042",
"manufacture_date": "2026-04-12",
"expiry_date": "2027-04-12",
"vendor_ref": "supplier-bayer",
"country_of_origin": "DE",
"regulatory_notes": "..."
}
}
In v1 this row is stored in the reserved lot table; behavior activates v1.6. A v1 caller can pre-load lot data at onboarding so a v1.6 cutover does not require a bulk re-ingest.
Lot reference on inventory and movements¶
From v1.6, every inventory snapshot row and movement carries lot_id for lot-tracked SKUs:
POST /wms-ingest/v1/inventory/snapshot?mode=bulk&scope=FULL
{
"snapshot": {
"warehouse_ref": "wh-tokyo-01",
"items": [
{
"consignor_id": "pharma-co",
"sku_ref": "PHARMA-001",
"location_ref": "BIN-COLD-01",
"lot_ref": "LOT-2026-Q2-0042", ◄── required when sku.track_by ∈ {LOT, LOT_AND_SERIAL}
"qty": 500
}
]
}
}
For a lot-tracked SKU, a snapshot row without lot_ref is rejected (MISSING_LOT_REFERENCE). For a non-lot-tracked SKU (track_by = NONE), lot_ref must be absent (UNEXPECTED_LOT_REFERENCE).
In v1 (before v1.6 activation), this validation is a soft check — present-but-unrecognized lot references are accepted into the schema; behavior switches to strict on Phase 14 cutover, with a 60-day “soft warning” window communicated in the v1.6 release notes.
FEFO picking — a Rule, not new code¶
Once v1.6 is live, “pick the lot closest to expiry” (FEFO — First Expired, First Out) is a flow-service Pull Rule, not a special-cased lot-aware code path. The Rule reads lot.expiry_date and selects the qualifying inventory row whose expiry is earliest, subject to min_shelf_life_days_at_ship. The Rule lives in flow-service per WMS ADR-0029.
Per-tenant Rule configuration is out of scope for this ingest contract; the ingest API only stores the lot data the Rule will later evaluate.
Recall correlation¶
When a recall notice arrives, the upstream registers an affected lot range and FG.AI surfaces every movement that touched a lot in that range. The traversal uses the lot_id column on every movement row. From the ingest API surface, recall integration is the read side — the upstream subscribes to inventory.adjusted webhooks filtered by lot_id and reconstructs genealogy from movements queried via the per-lot history endpoint (v1.6).
Anti-patterns¶
Treating track_by as configurable post-launch. It is immutable after first movement. Plan at onboarding.
Reusing a lot_id across consignors. Lots are scoped by consignor_id; two consignors may legitimately use the same vendor lot but they’re distinct master records. Always include consignor_id in /master/lots upserts.
Sending lot data for SKUs with track_by: NONE. Quarantined as UNEXPECTED_LOT_REFERENCE. Set track_by: LOT first.
Expecting FEFO picking in v1. v1 picks are FIFO-by-receive-time only. FEFO ships v1.6.