By Isaac Muthui, CEO, Tamalaki Business Network Ltd. Originally published at factoraerp.com.
Most factory systems store an aflatoxin result as a PDF attached to a purchase order or a supplier record. That answers "did we get a certificate?". It does not answer the question a regulator or a QA lead asks after a market sample fails: which flour batches used grain from the lots that tested high, and where did that flour go?
An aflatoxin result is only useful if it belongs to something you can trace: a lot of grain that arrived on a specific truck, sat in a specific silo, and was consumed by specific production batches. This post walks through how we think about modelling that in an ERP for food and agro manufacturers. The operational checklist behind it is in the original article.
What the domain rules say
The requirements come from food safety standards, not from software people:
- EAS 44:2011 (milled maize products) sets a maximum of 10 ppb total aflatoxin and 5 ppb aflatoxin B1 (ISO 16050), 13% maximum moisture, and sampling according to ISO 13690. Source
- Codex CXC 51-2003 (mycotoxins in cereals) says the intermittent presence of mycotoxins "is to be expected", asks for validated methods and sampling plans, and for procedures to handle suspect grain "through segregation, reconditioning, recall or diversion". It warns that small quantities of highly contaminated grain "can greatly increase mycotoxin levels" in otherwise good grain, and that grain should be tested at loading or unloading before storage at the processing plant. Source
Read as a spec: results attach to lots, lots have a status that gates their use, mixing is an event you must record, and you need to walk the graph from a finished batch back to suppliers and forward to customers.
The data model
A simplified version of the entities:
GrainLot id, supplier_id, vehicle_reg, received_at, net_kg, status
status: received | on_hold | accepted | accepted_for_reconditioning | rejected
LotTest id, lot_id, type (moisture | aflatoxin_total | aflatoxin_b1 | visual),
value, unit, method, kit_lot, tested_by, tested_at, lab (internal | external)
StorageMove id, lot_id, from_location, to_location (silo/store), kg, moved_at
StorageReading id, location_id, temperature_c, moisture_pct, read_at, read_by
Batch id, product_id, produced_at, line_id
BatchInput batch_id, location_id or lot_id, kg
BatchTest id, batch_id, type, value, unit, method, tested_at
Dispatch id, batch_id, customer_id, kg, dispatched_at
Three design choices matter more than the field list.
1. Status is derived from tests and decisions, not typed in. A lot arrives as received. It cannot move to accepted until the required tests for that supplier and season exist and pass, and a named person records the decision. A failed or missing result puts it on_hold, and stock on hold cannot be issued to production. That turns the intake specification into an enforced rule instead of a sticky note at the weighbridge.
2. Silos are where traceability gets hard. Once three trucks go into one silo, the grain is mixed. You do not need a perfect physical model of grain flow. You need a conservative one: record every StorageMove into a silo, and treat every batch milled from that silo during a window as potentially containing any lot that was in it. When a market sample fails, over-including a few batches is fine. Missing one is not.
3. Blending is a recorded event, not a fix. The tempting shortcut with a high lot is to mix it into clean grain until the average passes. The rules say no. In the model, a lot on_hold simply has no valid path into a silo that feeds food production. Reconditioning or diversion is a separate, approved status with its own destination.
The query that matters
When a market sample of a flour batch fails, the questions are:
-- 1. Which grain lots could have gone into this batch?
SELECT DISTINCT sm.lot_id
FROM batch_inputs bi
JOIN storage_moves sm ON sm.to_location = bi.location_id
WHERE bi.batch_id = :failed_batch
AND sm.moved_at <= (SELECT produced_at FROM batches WHERE id = :failed_batch);
-- 2. Which other batches drew from the same silos in the same window?
-- 3. Which customers received those batches, and how much is still in our stores?
With the graph in place, these run in seconds. Without it, they take a team a weekend with gate books, lab sheets and dispatch notes, which is exactly when you are least able to afford it.
Storage monitoring as a time series
Codex says a 2-3°C rise in stored grain temperature may indicate mould or insects. That is a classic time-series alert: store StorageReading rows per location, compute the change against the last few readings, and raise a non-conformance with an owner when the rise crosses your threshold. The alert is only as good as the reading schedule, so missing readings should be an alert too.
What to keep simple
- The intake test form should be a phone form at the weighbridge, with a server timestamp and the lot number pre-filled. If it takes five minutes, it will be filled in at the end of the shift from memory.
- Keep limits, test frequencies and supplier risk levels as configuration, because they come from your HACCP plan, your customers and KEBS, not from code.
- Store the method and kit lot with each rapid test result. When you compare rapid results with an external laboratory, you will want to know which kit produced which number.
The general lesson: in food manufacturing, the useful record is not "a test exists" but "this lot, with this result, went here, and became that". Once tests, holds and silo moves are facts tied to lots, "where did the bad grain go?" becomes a query.
The full 8-step aflatoxin control checklist, with the EAS 44 and Codex references, is on the FactoraERP blog.
Isaac Muthui, CEO, Tamalaki Business Network Ltd
Top comments (0)