DEV Community

Isaac Mwendwa Muthui
Isaac Mwendwa Muthui

Posted on Originally published at factoraerp.com

Cleaning Is a State, Not a Task: Modelling Sanitation Records, Line Release and Failed-Clean Holds in a Food ERP

By Isaac Muthui, CEO, Tamalaki Business Network Ltd. Originally published at factoraerp.com.

Most factory software models cleaning as a task. There is a checklist row, someone ticks "done", and the record stores a timestamp and a name. That is enough to show that cleaning happened. It is not enough to answer the question that actually matters when something goes wrong: was this line clean when this batch was made?

That question comes up when a hygiene swab fails, a customer complains about spoilage, or a clean-in-place (CIP) cycle turns out to have run at half the detergent strength for a week. At that point, a list of ticked tasks does not help. You need to know the cleaning state of each line over time, and which production runs overlapped with a bad state.

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 are not invented by software people. The Codex General Principles of Food Hygiene (CXC 1-1969) asks for written cleaning procedures that specify the areas and equipment, responsibility, method and frequency, and monitoring and verification activities. It says monitoring could include pH, water temperature, conductivity and chemical concentration, and that verification could include "protein and allergen test swabs, or microbiological testing for indicator organisms".

EAS 39:2001, the East African hygiene code for food manufacturing, asks for cleaning records and "evidence that the cleaning programme has been adhered to" (6.3.2), a procedure to check cleaning effectiveness by rinse water analysis or swabbing (6.3.3), and lists the strength of active ingredients and the temperature of each washing cycle among critical parameters to record (4.3.5). Kenya's Food Hygiene Regulations under Cap. 254 require equipment to be cleaned and sanitised before use, or on a predetermined schedule in continuous operations.

Read those as a spec and three entities fall out: a cleaning event with measured parameters, a release decision, and a verification result that can arrive later and change what we believe about the past.

The core entities

A simplified shape:

SanitationItem        (line, machine, area; risk class; frequency; zone)
SanitationProcedure   (item -> versioned SSOP: chemical, target concentration,
                       temperature, contact time, steps)
CleaningEvent         (item, procedure_version, started_at, finished_at,
                       performed_by, method: manual | foam | cip)
CleaningReading       (event, parameter: concentration | temperature |
                       duration | conductivity | ph, step, value, unit,
                       instrument_id, within_spec)
LineRelease           (item, event, inspected_by, released_at,
                       result: released | rejected)
VerificationResult    (item, event?, sampled_at, method: atp | protein |
                       allergen | micro | rinse_water, site, value, pass)
ProductionRun         (line, batch/lot, started_at, ended_at)
Enter fullscreen mode Exit fullscreen mode

A few design choices matter more than the field list.

1. Readings belong to the event, not to a free-text note. If concentration and temperature are typed into a comment field, you cannot query "every CIP cycle on filler 2 where caustic concentration was below target". Store each reading as a row with a parameter, a value, a unit and the instrument that produced it. Linking the instrument means a later calibration failure on that conductivity meter can be joined back to the readings it produced.

2. Version the procedure. SSOPs change: a new chemical, a new contact time, a new machine part. A cleaning event should point at the procedure version in force when it ran, so "within spec" is evaluated against the right target.

3. Release is its own record, signed by someone else. The person who cleaned should not be the person who releases. Model LineRelease separately from CleaningEvent, with its own actor, and make production start depend on it. In practice that means a production run on a line cannot be opened unless the latest release for that line is released and newer than the last production run or changeover.

4. Verification is asynchronous. An ATP swab gives an answer in minutes; a laboratory micro swab may come back days later. Treat VerificationResult as an event that can arrive after production has already used the line, and design for it to re-open history.

The query that matters: the at-risk window

When a verification fails, or a reading is found out of spec, the system has to compute an at-risk window:

window_start = finished_at of the last cleaning event on this item
               that was within spec AND verified good (or released, by policy)
window_end   = the next cleaning event that was within spec and verified good
               (or now, if none)

at_risk_runs = ProductionRun where line = item.line
               and [started_at, ended_at] overlaps [window_start, window_end]
Enter fullscreen mode Exit fullscreen mode

Every lot produced in at_risk_runs is a candidate for a QA hold. The size of that window is a direct function of how often you verify. Verify every day and the window is a day; verify monthly and the window is a month of production.

Two policy questions are worth making explicit in configuration rather than code:

  • Does a visual release count as "good" for window purposes, or only a passed swab? Plants differ, and auditors will ask what your rule is.
  • Do you hold automatically, or raise a task for QA to decide? We lean towards automatically creating a non-conformance and proposing holds, with QA confirming, because food safety decisions need a human owner.

Holds and non-conformances, not deletes

When the window is computed, the useful actions are:

  • create a non-conformance record linked to the failed reading or verification, with an owner, cause and corrective action;
  • link the affected lots and move them to a QA hold stock status, so they cannot be picked or invoiced;
  • if QA later rejects some of them, write them off through a stock adjustment with a reason code, so the loss shows up as "failed clean" in yield and cost reports instead of an unexplained stock variance.

Nothing gets overwritten. The cleaning event stays as recorded; the verification result and the non-conformance are added on top. That append-only shape is what makes the record defensible in an audit.

Edge cases we have learned to respect

  • Shared CIP circuits. One CIP set may clean several tanks and lines. Model the circuit as the item that is cleaned, and map which production assets it covers, or you will miss lots.
  • Dry plants. Flour mills and spice packers mostly dry clean, and wet-cleaned parts must be dry before restart (EAS 39, 6.3.13). A "dry confirmed" check belongs in the release, not just the clean.
  • Allergen changeovers. An allergen swab failure should block release for the next non-allergen product specifically, not just the line in general.
  • Offline floors. Many factories have patchy connectivity. Cleaning readings captured on a tablet need to queue locally and sync with original timestamps, or your at-risk window is built on upload times.

The takeaway

If your schema can only say "cleaning done at 21:40 by J.", it can prove effort, not effect. Model cleaning events with measured readings, separate the release decision, treat verification as late-arriving evidence, and make the at-risk window a first-class query. Then a failed swab becomes a list of lots on hold in minutes, not a week of searching through paper.

The full operational checklist, with the Codex, EAS 39 and Kenyan regulation references and an FAQ, is on the FactoraERP blog.

Top comments (0)