DEV Community

Cover image for Building a Four-Layer Data Model for FSMA 204 Cold Chain Traceability
applekoiot
applekoiot

Posted on • Originally published at blog.appleko.io

Building a Four-Layer Data Model for FSMA 204 Cold Chain Traceability

The FDA just pushed the FSMA 204 compliance deadline from January 2026 to July 2028. If you work anywhere near food supply chains or cold chain IoT, you've probably heard the collective sigh of relief.

But here's the thing I keep seeing from the hardware side: the delay isn't a sensor problem. It's a data model problem. Companies have thermometers everywhere. What they don't have is a schema that maps sensor readings to the specific Critical Tracking Events (CTEs) and Key Data Elements (KDEs) the FDA actually requires.

I've been building IoT tracking devices for cold chain and logistics for over 20 years, shipping to 100+ countries. The pattern is always the same — plenty of telemetry, zero traceability architecture.

This post walks through the four-layer data model I recommend.

The Core Problem

FSMA 204 requires companies that handle foods on the Food Traceability List to produce an electronic, sortable spreadsheet of traceability records within 24 hours of an FDA request.

That means your system needs to answer:

Given lot_code = "LOT-2026-04-0042"
Return:
  - Every CTE this lot passed through
  - KDEs at each CTE (who, what, where, when, from/to)
  - Associated sensor telemetry per transit segment
  - Any anomalies (excursions, data gaps, lot mismatches)
Format: sortable spreadsheet
Time budget: < 24 hours
Enter fullscreen mode Exit fullscreen mode

Most ERPs can't do this. They track transactions, not traceability events.

The Four-Layer Architecture

Here's the model that works:

Layer 1: Entity (Master Data)

Your reference tables. These change slowly.

Locations:  GLN or FFRN identifier, address, type
Products:   GTIN, description, FTL category
Lots:       Traceability Lot Code (TLC), product_id, created_at
Actors:     Legal entity, role (grower/processor/distributor/retailer)
Enter fullscreen mode Exit fullscreen mode

Layer 2: Event (CTE Records)

Each custody change or transformation creates one event record.

{
  "cte_type": "receiving",
  "timestamp": "2026-04-15T14:30:00Z",
  "location_gln": "0012345000010",
  "actor_id": "distributor-acme",
  "lot_code": "LOT-2026-04-0042",
  "product_gtin": "00012345678905",
  "source_actor": "processor-freshco",
  "quantity": 120,
  "unit": "cases",
  "reference_doc": "PO-88921",
  "telemetry_session_id": "sess-7f3a9b"
}
Enter fullscreen mode Exit fullscreen mode

The seven core CTEs: growing, receiving, transforming/creating, shipping, receiving (again at next node), and first land-based receiver.

Layer 3: Telemetry (Sensor Data)

This is the IoT layer. Continuous time-series data linked to events via telemetry_session_id.

{
  "session_id": "sess-7f3a9b",
  "sensor_id": "GPT29-unit-4471",
  "readings": [
    { "ts": "2026-04-15T08:00:00Z", "temp_c": 2.1, "rh_pct": 65, "lat": 32.78, "lng": -96.80 },
    { "ts": "2026-04-15T08:05:00Z", "temp_c": 2.3, "rh_pct": 64, "lat": 32.79, "lng": -96.78 },
    { "ts": "2026-04-15T08:10:00Z", "temp_c": 5.8, "rh_pct": 71, "lat": 32.80, "lng": -96.77 }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Key design decisions:

  • Store timestamps in UTC. Always. Sensor clock drift is a real problem — synchronize against network time at each transmission.
  • Use a time-series database (InfluxDB, TimescaleDB) for telemetry, not your relational DB.
  • Link via session ID, not by timestamp correlation. Timestamps drift; session IDs don't.

Layer 4: Evidence (Audit Exports)

The layer everyone forgets. This is what the FDA actually receives.

-- Simplified: generate the sortable spreadsheet
SELECT
  e.cte_type,
  e.timestamp,
  l.address AS location,
  p.description AS product,
  e.lot_code,
  e.source_actor,
  a.name AS performed_by,
  anomaly.type AS anomaly_flag
FROM events e
JOIN locations l ON e.location_gln = l.gln
JOIN products p ON e.product_gtin = p.gtin
JOIN actors a ON e.actor_id = a.id
LEFT JOIN anomalies anomaly ON anomaly.event_id = e.id
WHERE e.lot_code = 'LOT-2026-04-0042'
ORDER BY e.timestamp;
Enter fullscreen mode Exit fullscreen mode

Design this query on day one. If you can't produce the output, your other three layers don't matter.

Designing for Anomalies

Normal operations are easy. Here's what actually breaks:

Temperature excursion: A reading crosses your threshold (say, >4°C for refrigerated product). Auto-generate an anomaly record:

{
  "type": "temperature_excursion",
  "session_id": "sess-7f3a9b",
  "trigger_reading": { "ts": "2026-04-15T08:10:00Z", "temp_c": 5.8 },
  "duration_minutes": 23,
  "lot_codes_affected": ["LOT-2026-04-0042"],
  "corrective_action": null,
  "disposition": "pending_review"
}
Enter fullscreen mode Exit fullscreen mode

Data gap: Sensor stops reporting for >15 minutes. Flag it. "No data" ≠ "data within range." Auditors know the difference.

Lot mismatch: Receiving CTE lot code doesn't match shipping CTE. Generate a reconciliation exception — don't silently accept it.

The 90-Day Sprint

Phase Days Deliverable
Audit 1-30 Data gap analysis: which KDEs you can produce today vs. what's missing
Prototype 31-60 Four-layer schema + one route with live IoT sensors + 24hr export test
Partner onboard 61-90 Top 5 partners sending KDEs in agreed format (GS1 EPCIS or CSV template)

The hardest part isn't your internal systems. It's getting accurate KDEs from partners who may still be on paper. Start those conversations now.

What Approach Has Worked for You?

I'm curious how others are tackling the data architecture side of FSMA 204. Are you building on top of existing ERP schemas? Standing up a separate traceability layer? Using EPCIS natively?

If you're working on the IoT sensor side of this problem — connecting device telemetry to traceability events — I'd be happy to compare notes.


This article was written with AI assistance for research and drafting. The architecture recommendations are based on 20+ years of IoT cold chain deployment experience.

Top comments (0)