By Isaac Muthui, CEO, Tamalaki Business Network Ltd. Originally published at factoraerp.com.
A metal detector on a food packing line is a sensor with a pass/fail output. Every shift, someone pushes a certified test piece through it inside a pack and writes down whether the reject arm fired. Most factory systems store that as a checklist row: timestamp, operator, "OK".
That record answers "was the check done?". It does not answer the question that matters when a check fails: which lots were packed between the last passing check and this failing one? That interval is the blast radius. Everything inside it has to be held and re-inspected; everything outside it can keep moving.
This post walks through how we think about modelling that in an ERP for food and agro manufacturers in East Africa. 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:
- Codex CXC 1-1969 (section 13.2.5) asks for systems to prevent hard or sharp foreign objects such as glass, metal shards, plastic and wood, and for "appropriately calibrated" detection devices such as metal or X-ray detectors where necessary. Its HACCP annex says CCP monitoring should detect a deviation in time "to allow isolation of the affected products", and uses "a broken sieve" as an example deviation. Source
- EAS 39:2001 (4.4.7) says a metal detector should be installed where appropriate on consumer lines, capable of detecting 2 mm ferrous and 2.5 mm non-ferrous spheres unless the product makes that impossible, with sensitivity checked at least once per 8-hour shift by a designated person. Clause 4.6.5 asks for automatic rejection into a locked container, or a line stop. Source
- Kenya's Food Hygiene Regulations, 1978 require equipment to be designed and used so as to prevent contamination by metal fragments, and contaminated food to be rejected or adequately treated. Source
Read as a spec, that gives us: checks are periodic, each check has an outcome, a failure has to be resolvable to a set of affected product, and that product has to be isolated.
Model checks as events on a control point, not as tasks
Instead of a free-floating checklist, attach each check to a control point on a line:
ControlPoint
id, line_id, type -- metal_detector | xray | sieve | magnet | glass_item
limits -- e.g. {fe_mm: 2.0, non_fe_mm: 2.5, ss_mm: ...}
max_interval_minutes -- e.g. 480 for "once per 8-hour shift"
ControlCheck
id, control_point_id, checked_at, checked_by
result -- pass | fail
readings -- test pieces used, reject confirmed, sieve intact, magnet findings
Two derived facts fall out of this immediately:
-
Last good check per control point: the latest
ControlCheckwithresult = pass. -
Overdue:
now - last_check.checked_at > max_interval_minutes. An overdue detector is treated like a failed one for release decisions, because you cannot prove it was working.
Production output already carries the time dimension
Lots are produced on a line over a time window. If output records carry line_id, started_at and ended_at, the blast radius of a failed check is a range query:
SELECT lot_id
FROM production_output
WHERE line_id = :line
AND ended_at > :last_good_check_at
AND started_at <= :failed_check_at;
The important design choice is that the lower bound is the last passing check, not the start of the shift or the start of the day. That is what keeps a single failure from turning into a full-day write-off, and it only works if passing checks are recorded with real timestamps.
A failure opens a non-conformance and places holds
When a check fails, three things happen in one transaction:
- A non-conformance record is opened with an owner, the control point, and the failing check.
- The lots returned by the range query are linked to it.
- Each linked lot's stock status moves to QA hold, so it cannot be picked, transferred or dispatched until QA releases it.
Release is a decision per lot: re-inspected on a working detector and released, reworked, or destroyed. Destroyed quantities leave stock through an adjustment with a reason code that points back at the non-conformance, so the loss shows up as an explained foreign body write-off rather than an unexplained stock count difference or a bad batch yield.
Breakages and complaints fit the same shape
- A glass or hard plastic breakage is a control point event with
type = glass_item. The affected set is defined by area and time rather than a single line, but it still resolves to lots and still opens a non-conformance. - A customer complaint about a foreign body runs the query in reverse: start from the lot code on the pack, find the line and time window, then check which control checks bracket it. If the bracketing checks all passed, the investigation moves upstream (sieves, magnets, maintenance). If one failed or was overdue, the hold logic above probably should have fired.
What to keep simple
- You do not need to integrate every detector's PLC on day one. A simple form at the line that records the test result with a server timestamp gets most of the value.
- Keep limits on the control point, so changing a sensitivity spec is configuration, not code.
- Treat trending (rejects per line per week, magnet findings over time) as a report over
ControlCheck, not a separate module.
The general lesson: in food manufacturing, the useful unit is not "the task was done" but "the control was in a known good state over this interval". Once checks are events with timestamps tied to lines and lots, the hard question after an incident becomes a query.
The full 8-step foreign body control checklist, with the Codex, EAS 39 and Kenyan regulation references, is on the FactoraERP blog.
Isaac Muthui, CEO, Tamalaki Business Network Ltd









