DEV Community

Isaac Mwendwa Muthui
Isaac Mwendwa Muthui

Posted on Originally published at factoraerp.com

Model Instruments as First-Class Records: Calibration Status, Check Readings and Out-of-Tolerance Holds in a Food ERP

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

Most factory software treats a measurement as a number. A batch record stores pasteurisation_temp = 72.4, an intake record stores net_kg = 1240, a pack-weight sample stores grams = 503. Nothing in the schema says which instrument produced the number, or whether that instrument was trustworthy at the time.

That is fine until a QA officer finds that a thermometer has been reading high, or a weighbridge fails its check. Then the questions arrive all at once: which batches did that thermometer sign off? Which supplier deliveries did that scale weigh? Since when? The data to answer them usually exists somewhere, but not in a shape you can query.

This is a data modelling problem before it is a quality problem.

What the standards expect

The Codex General Principles of Food Hygiene (CXC 1-1969) says temperature monitoring and recording devices should be checked for accuracy and calibrated at regular intervals or as needed, and lists "calibrating or checking the accuracy of instruments used for monitoring and/or verification" among HACCP verification activities. The East African hygiene code, EAS 39:2001, says instruments monitoring critical parameters should be calibrated at appropriate intervals so their accuracy stays within defined limits, and lists the calibration status of devices used to assure food safety among the records a plant must keep.

For scales used in trade, Kenya adds legal metrology. The State Department for Trade says every piece of equipment used for trade must bear a valid stamp of verification under the Weights and Measures Act (Cap. 513), and that equipment suspected to be inaccurate should be taken out of use.

Translated into requirements for a system:

  1. Every reading must be attributable to an instrument.
  2. Every instrument must have a status at any point in time: in calibration, due, overdue, failed, out of service.
  3. A failed check must be able to find every record that depended on that instrument since its last good check.
  4. Using an overdue or failed instrument should be hard, not just discouraged.

A minimal schema

This is a design sketch, not any specific product's schema. Postgres-flavoured SQL:

create table instrument (
  id              text primary key,          -- the tag on the device, e.g. 'TH-PAST-01'
  kind            text not null,             -- scale, thermometer, ph_meter, metal_detector, ...
  location        text not null,
  use_flags       text[] not null,           -- {'ccp','trade','costing'}
  tolerance       numeric not null,          -- in the instrument's unit
  unit            text not null,
  cal_interval    interval not null,         -- external calibration
  check_interval  interval not null,         -- in-house check, e.g. '8 hours'
  in_service      boolean not null default true
);

create table calibration_event (             -- external, by an accredited lab or W&M inspector
  id            bigserial primary key,
  instrument_id text references instrument(id),
  performed_at  timestamptz not null,
  provider      text not null,
  certificate   text not null,               -- certificate or verification number
  as_found_error numeric,                    -- error before adjustment
  as_left_error  numeric,                    -- error after adjustment
  uncertainty    numeric,
  passed        boolean not null
);

create table check_reading (                 -- in-house: check weight, reference thermometer, test piece
  id            bigserial primary key,
  instrument_id text references instrument(id),
  checked_at    timestamptz not null,
  expected      numeric not null,
  observed      numeric not null,
  checked_by    text not null,
  passed        boolean not null           -- set by trigger from instrument.tolerance
);
Enter fullscreen mode Exit fullscreen mode

passed on a check should be derived from the instrument's tolerance, not typed in by the operator. Generated columns cannot reference another table, so set it in a trigger:

create or replace function set_check_result() returns trigger as $$
begin
  new.passed := abs(new.observed - new.expected) <=
                (select tolerance from instrument where id = new.instrument_id);
  return new;
end $$ language plpgsql;
Enter fullscreen mode Exit fullscreen mode

The important detail is as_found_error. Most teams file calibration certificates and only look at the "as left" result: the instrument is fine now. The "as found" value tells you whether it was fine before, which is the question that matters for product already made.

Every measurement points at an instrument

The change that unlocks everything is a foreign key on the measurement itself:

create table measurement (
  id            bigserial primary key,
  instrument_id text not null references instrument(id),
  taken_at      timestamptz not null,
  value         numeric not null,
  context_type  text not null,     -- 'batch_ccp', 'goods_receipt', 'pack_sample', ...
  context_id    text not null      -- batch number, GRN number, ...
);
Enter fullscreen mode Exit fullscreen mode

If your intake form, batch record and pack-weight sampling all write through this table (or carry instrument_id themselves), the "what did this instrument touch?" question becomes a query instead of an investigation.

Status is derived, not stored

Do not keep a status column that someone has to remember to update. Derive it:

create view instrument_status as
select i.id,
       case
         when not i.in_service then 'out_of_service'
         when last_fail.checked_at > coalesce(last_pass.checked_at, '-infinity') then 'failed'
         when last_cal.performed_at + i.cal_interval < now() then 'overdue'
         when last_pass.checked_at + i.check_interval < now() then 'check_due'
         else 'ok'
       end as status
from instrument i
left join lateral (select performed_at from calibration_event c
                   where c.instrument_id = i.id and c.passed
                   order by performed_at desc limit 1) last_cal on true
left join lateral (select checked_at from check_reading r
                   where r.instrument_id = i.id and r.passed
                   order by checked_at desc limit 1) last_pass on true
left join lateral (select checked_at from check_reading r
                   where r.instrument_id = i.id and not r.passed
                   order by checked_at desc limit 1) last_fail on true;
Enter fullscreen mode Exit fullscreen mode

Then gate writes. A CCP measurement or a goods receipt from an instrument whose status is not ok should be rejected, or at least accepted only with a supervisor override that is itself recorded:

-- in the measurement insert path
if (select status from instrument_status where id = new.instrument_id) <> 'ok' then
  raise exception 'Instrument % is not in a usable state', new.instrument_id;
end if;
Enter fullscreen mode Exit fullscreen mode

That is the database version of the floor rule "overdue means out of use".

The window-at-risk query

When a check fails, the window at risk is everything that instrument measured since its last passing check (or since the last calibration whose as-found error was within tolerance):

with failed as (
  select instrument_id, checked_at as failed_at
  from check_reading where id = $1
),
last_good as (
  select max(r.checked_at) as good_at
  from check_reading r, failed f
  where r.instrument_id = f.instrument_id
    and r.passed and r.checked_at < f.failed_at
)
select m.context_type, m.context_id, min(m.taken_at), max(m.taken_at), count(*)
from measurement m, failed f, last_good g
where m.instrument_id = f.instrument_id
  and m.taken_at > g.good_at
  and m.taken_at <= f.failed_at
group by m.context_type, m.context_id;
Enter fullscreen mode Exit fullscreen mode

The result is the list to act on: batch numbers to place on QA hold, goods receipts whose quantities (and supplier payments) need review, pack-weight samples to discount. It is also why check frequency is a product decision as much as a QA one: the shorter the interval, the smaller this result set.

Design notes from the floor

  • Use the device tag as the primary key. Operators read the tag; they will not read a UUID.
  • Make the check reading fast to capture. Expected value pre-filled from the check weight or reference in use, observed value typed or read from the device, one tap to submit. If it takes longer than the check itself, it will be skipped.
  • Treat a failed check as an event that opens work. Create a non-conformance with an owner, and attach the window-at-risk result to it, rather than sending an email.
  • Keep trade verification separate from technical calibration. A Weights and Measures stamp and an ISO/IEC 17025 calibration certificate are different documents with different authorities. Store both, with their own expiry.
  • Keep history immutable. Corrections are new rows. An auditor will ask what the record said on the day, not what it says now.

Where this fits

At FactoraERP we work with food and agro manufacturers in East Africa, where the same intake scale often feeds supplier payments, stock and yield, and where a QA hold has to reach the right lots quickly. FactoraERP tracks production batches and lots, stock status including QA holds, non-conformance records and stock adjustments with reason codes, which is the part of this model a failed instrument check has to reach.

The full, non-technical calibration checklist for factory teams, with the Codex, EAS 39 and Kenyan Weights and Measures references, is on the FactoraERP blog.

How do you link measurements to instruments in your systems today: a foreign key, a free-text field, or not at all?

Top comments (0)