Skip to content

How much of the window did the MES see

Explanation. Why every KPI here carries a second number, what that number means, and how to read the ledger behind it.

Every OEE figure has a denominator. Ask most MES products what theirs is and the answer is the window — the shift, the last eight hours, the day. That is only true if somebody watched the whole window, and on a real plant nobody did. The agent was restarted. A switch rebooted. A certificate expired. An engineer unplugged the wrong port. The window went on; the watching did not.

So this MES states two numbers, always, side by side:

says
Availability run time ÷ the part of the window this MES actually watched
Coverage how much of the window that was

92 % availability over forty watched minutes of an eight-hour shift is not the same claim as 92 % over the shift, and coverage is the only thing on a screen that tells those two apart.

The ledger

Coverage is not computed on its own. It falls out of a ledger: per machine, per window, a list of intervals that tile the window exactly — every second of it in one interval, no second in two — and each interval carries one disposition.

Disposition Means
observed_running the MES watched the machine and it was running
observed_stopped_labelled the MES watched it stopped, and somebody said why
observed_stopped_unlabelled the MES watched it stopped, and nobody has said why yet
not_observed nobody was watching

Availability is the first of those over the sum of the three observed_ ones. Coverage is that sum over the window. Nothing is inferred and nothing is filled in: if the MES holds no record of a stretch of time, that stretch is not_observed and stays there.

An OEE window whose seconds do not add up is a bug, not a rounding difference. A test asserts the tiling on every path, and fsmes oee explain refuses with a non-zero exit code if the intervals it just printed do not sum to the window it printed them for.

What is derived, and what only carries coverage

Availability is derived from the ledger: its numerator and denominator are both sums of ledger intervals, so availability and coverage can never disagree about the same window.

Performance and quality keep their own arithmetic — counted units against a rated cycle time, and good against good-plus-scrap — and carry the same window's coverage beside them rather than being rebuilt out of it. That is stated rather than glossed: a low-coverage window's performance figure is built on the run time the ledger measured and on counts booked across the whole window, and the honest reading of it is "over the part of this window anybody watched". Deriving their inputs from the ledger too is a change of its own and has not been done.

Where a plant sets a floor, all four figures are withheld together. A window too thin to state availability for is too thin to state OEE for.

Where the unwatched time went

Every not_observed interval carries a cause.

Cause Means
before_first_sample earlier than the first state or connection this MES ever recorded for this machine. Not a fault — a machine commissioned this morning has most of an eight-hour window in here — but it is not watched time either
disconnected a disconnection this MES recorded: it could not see the machine. The sentence whatever noticed it wrote travels with the interval (losing sight of a machine)
after_last_sample later than the last thing this MES recorded, with nothing left open. The machine stopped being reported on and nothing said why
no_state_recorded inside the watching period, no disconnection recorded, and the state history has a hole anyway. An agent killed outright writes no disconnection on its way down, and this is what that looks like

Why not "the agent was down" and "the tag went stale"

Because this MES cannot tell those apart from what it has written down. A disconnection interval holds a sentence somebody's code wrote — the OPC server did not answer: timed out — and the endpoint that was being dialled. Sorting that free text into a taxonomy would be the MES claiming to know which of several things happened when all it holds is that the link was gone. So disconnected is one cause and the recorded sentence is printed beside it, unaltered, for a person to read.

The day a plant's OPC server publishes a heartbeat tag, per-machine staleness becomes a positive signal rather than an inference (decision 0030 already says so, under "to revisit") and this table gains a row with evidence behind it. Not before.

How precisely was the watched time measured

Separate question, and the ledger does not answer it — it states it. Each observed interval carries sample_interval_seconds: the cadence this plant's machine layer is configured to publish at (MES_OPC_PUBLISH_MS), together with a sentence saying that is what the number is.

It matters because a machine that changes state every three seconds, watched on a fifteen-second grid, loses run time to the grid — measured, on a real lab run, in decision 0026. Coverage asks whether time was observed; the sample interval is about how precisely. They compose: a future change-driven capture makes the observed intervals more precise and does not change the ledger's shape.

The coverage floor

By default nothing is withheld. Every figure prints with its coverage beside it and the plant decides what to make of it. There is no default floor, because a number vanishing off a screen because of a threshold nobody chose is its own kind of dishonesty.

A plant that would rather be told unknown than shown a figure measured over eleven minutes of a shift says so in its pack:

[oee]
coverage_floor = 0.8

A share, greater than 0 and at most 1; fsmes pack check refuses anything else, including 0 — no floor is the key being absent. With a floor set, a window whose coverage is below it comes back as unknown — in the API, in /metrics, and on the screens, which print the ledger's summary instead of drawing a smaller bar. A shorter bar would read as a worse machine, and what actually happened is that nobody watched it.

What is withheld is the figure, never the evidence. Run time, counts, seconds by state and the whole ledger stay on the object.

The three lab packs (bottling, machining, finewire) set a floor, so the behaviour is visible in the fleet. The cutlery demo deliberately does not, so the no-floor path is the one the public demo runs.

Reading the ledger

$ fsmes oee explain MIX01 2h
MIX01 — Mixer 01
  the last 2h: 2026-09-18 00:30:43 to 2026-09-18 02:30:43 UTC (2h 00m 00s  (7,200.0 s))

      from        to    seconds  disposition / cause
  00:30:43  00:50:42     1199.5  before_first_sample
                                   rule: earlier than the first state or connection this MES ever recorded here
  00:50:42  01:20:42     1800.0  observed_running
                                   rule: a recorded state interval said running
  01:20:42  01:30:42      600.0  observed_stopped_labelled
                                   rule: a recorded state interval said not running, and carried a reason
                                   said: blade change
  01:30:42  01:50:42     1200.0  observed_running
                                   rule: a recorded state interval said running
  01:50:42  02:05:42      900.0  disconnected
                                   rule: a recorded disconnection: this MES could not see the machine (decision 0030)
                                   said: the OPC server did not answer: timed out
  02:05:42  02:25:42     1200.0  observed_running
                                   rule: a recorded state interval said running
  02:25:42  02:30:43      300.5  no_state_recorded
                                   rule: inside the watching period, no disconnection recorded, and no state interval either
  7 interval(s)
  ...
  observed                       1h 20m 00s  (4,800.0 s)
  window                         2h 00m 00s  (7,200.0 s)
  coverage                       66.7%
  availability (run ÷ observed)  87.5%

The window is a span (8h, 90m) or a shift (current, previous, 2026-09-17/NIGHT). Every interval names the rule that produced it, on purpose: a plant engineer who disagrees with the availability figure should be able to see which interval they disagree about, and argue with the rule rather than with the number.

In /metrics

Availability and coverage are exported as a pair, per machine, over the last eight hours:

mes_equipment_coverage{plant="kc1",equipment="MIX01"} 0.667
mes_equipment_availability{plant="kc1",equipment="MIX01"} 0.875
mes_equipment_not_observed_seconds{plant="kc1",equipment="MIX01",cause="disconnected"} 900.0

They ship together so a Grafana panel cannot show one without being able to show the other.

An availability this MES cannot state is absent, not zero — too little watched time, or a window below this plant's floor. Absent is what Prometheus already means by unknown; a zero would put a machine nobody watched at the top of a worst-performer board. mes_coverage_floor is exported when a floor is set, so a panel can draw the bar the plant chose.

What this costs you, and why it is worth it

It makes your numbers lower, and sometimes it makes them disappear. Put this MES in shadow mode beside the system already running the plant and it will often report less availability than the incumbent — not because it watched a worse plant, but because it is subtracting the minutes it could not see and the incumbent is not.

That is the feature. A number you can trust has to be able to say when it cannot.

See also