Skip to content

Who names the reasons

At 06:10 a machine stops. Somebody has to say why, and until this feature existed the screen asked an open question and got an open answer: a text box whose placeholder read "e.g. jam at infeed, waiting on fitter", stored exactly as typed.

The downtime pareto then grouped on that string and on nothing else. So jam, Jam, jam at infeed and infed jam were four separate reasons, each too small to act on, and the real top reason on the line was invisible because it was spelled four ways. The operator did nothing wrong.

The other half of the same problem is that the list they should have been picking from did not exist, and there was nowhere to put it. A reason vocabulary is not equipment, not a material and not a routing, so masterdata.write did not cover it — and of the built-in roles only admin held any capability that wrote configuration at all. A process engineer who wanted to name six downtime reasons had to be made administrator of the whole plant, or had to find one.

Both halves are one failure: the plant's vocabulary had no author. This page is how it gets one.

The short version

  1. Somebody holding process.define drafts a reason, on Engineering › Configuration › Downtime reasons (/dashboard/reasons) or through the API. Nothing changes on the floor.
  2. It appears on the Waiting for you panel on /dashboard — for the people who can act on it, and for nobody else.
  3. Somebody holding process.approve puts it in force. The row records who and when.
  4. The station screen offers it at the next screen load, and the pareto groups on it.
  5. Undo is one move: approve the previous revision. Nothing is deleted.

The two capabilities

Capability What it grants Who holds it as shipped
process.define Draft the plant's process vocabulary admin, and the agent role
process.approve Put a vocabulary in force admin only

They are separate because drafting a list and putting it in front of every operator on the site are different jobs, and because an administrator can now compose a process engineer role that holds them without holding users.manage or masterdata.write. A role is a named bundle of capabilities and bundles are data, so that takes an afternoon on the admin screen and no deployment.

The agent role holds the drafting half and never the approving half — the same shape work instructions, triggers and setpoint adjustments already have. An assistant that reads a month of what operators typed and proposes six codes is the point of the feature; an assistant that signs its own proposal is not.

The screen

Engineering › Configuration › Downtime reasons (/dashboard/reasons) is where a plant's vocabulary is read and written. It is one row on the Engineering workspace's Configuration page (/dashboard/config/engineering), which is where every configurable thing in that workspace is listed - one Configuration entry per workspace, never one per setting, however many settings arrive. The address above is unchanged, so a bookmark still opens this screen directly.

Everything below can also be done with a token and curl, and was only possible that way until this screen existed.

The list is open to anybody who can see the plant — a vocabulary nobody can read is a vocabulary nobody can choose from — and shows every word the plant has ever had, not just the approved ones. A word that is on the list and has an unsigned change waiting says both things at once, because an engineer told only the second would read a word as off the floor while operators are still choosing it:

Column What it says
Reason the code the analysis groups on, and the name on the operator's button
Description the sentence shown beside it at the machine
Status where the word stands — on the list or retired — and beside it what is waiting on somebody: change waiting, retirement waiting. A word nobody has ever signed reads drafted, not signed
Rev the revision in force, and the one drafted behind it: 1 → 2
Drafted who wrote the newest revision, on whose behalf if an agent wrote it for somebody, and when
Signed who put the revision in force and when, or — where nothing has been signed yet
Labels how many recorded intervals carry the code

Four tiles above it count the plant's vocabulary — on the list, drafts waiting, retired, and every word in all — and the list states its own total under the table.

The form is only there for a person holding process.define, and it is absent rather than refusing: everyone else reads the list and sees no form at all. Three boxes — code, name, description — and Save draft.

  • A new word: type a code that does not exist yet.
  • The next revision of a word already in force: type its code (the box offers what the plant already has). New revision on the row fills the form for you.
  • Editing a draft nobody has signed: Edit draft on the row. Nobody is choosing from it yet, so it is edited in place rather than stacking up revisions.

Nothing in the form re-states what the server already checks. A code that is spelled wrong, taken, or a word the product owns comes back as the server's own sentence, shown as it was written — a second copy of the rule in JavaScript is the copy that drifts, and the one that drifts is the one the person reads.

Saving puts the draft on the Waiting for you panel and changes nothing on the floor. The screen says so where you are standing:

jam_infeed rev 1 drafted — it reaches the floor when somebody signs it on the Floor screen

Retiring from the screen

Retire appears only on a word that is actually on the list. It asks first, and the confirmation carries the number:

Retire jam_infeed? 412 recorded intervals carry it. They keep the label — retiring changes what may be chosen next, never what was chosen before. The retirement is a draft until somebody signs it.

That is the same number the API demands in labels_intervals, put in front of the person before they decide instead of arriving as a refusal afterwards. A code nothing carries says so plainly rather than showing a zero that reads like a missing figure.

The retirement is a draft like any other: the word stays on the list until somebody holding process.approve signs it.

What the screen does not do

It does not sign anything. Drafting and approving are separate powers and this is the drafting screen; putting a revision in force — including the undo, which is approving the previous revision — happens on the Waiting for you panel or through the API below. A screen that both drafted and signed would be one click away from being a table somebody edits.

Drafting one

The same act from a terminal:

curl -sX POST "$MES/equipment/downtime-reasons" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"code": "jam_infeed", "name": "Jam at the infeed",
       "description": "Something stuck where product enters the machine."}'

The code is what the analysis groups on and what leaves the MES: two to forty characters, lowercase, starting with a letter. The name is what the operator reads on the button. The description is the sentence shown beside it, which is how two reasons that read alike on a button can still be told apart at the machine.

Validated before approval, not after. A code is unique, it is spelled the way a topic segment is spelled, and it may not be a word the product already owns — a state (running, down), a capability, a role, a KPI. The list is read from the product itself rather than typed anywhere, so a word that becomes product vocabulary next month is protected the day it lands. A reason that spelled the same word as something the product owns would mean two things at once, and a fleet comparing two plants could not tell which.

Drafting the same code again edits the open draft: nobody is choosing from it yet.

Finding one waiting

This is the step the product's three existing approval lifecycles were missing. A work-instruction draft, a trigger draft and a proposed adjustment each wait on a screen somebody has to know to open, and nothing tells anybody — there is no inbox, no aggregate count and no mail, webhook or push of any kind. Meanwhile drafting sits with supervisors and agents while approving sits with administrators, so the proposer and the approver are usually different people.

GET /dashboard/pending-approvals answers what is waiting that I may act on, from the caller's live capabilities, and the Waiting for you panel on /dashboard renders it: the kind, the code, what it is, who drafted it and on whose behalf, how long it has waited, and one action that signs it. Counted, and with the whole queue's total rather than the page's.

The rule it answers to:

A pending item appears on the screen of the role that can act on it, counted, with its total — and on no screen that cannot act on it.

So a person who can approve nothing never sees the panel at all. And a draft nobody acts on waits, visibly: it does not expire, it does not go live by itself, and the only thing that changes with time is one column, so a forgotten draft reads as "11 days" rather than falling off the end of a list.

One kind is behind that endpoint today — the downtime vocabulary. Work instructions, triggers, setpoint adjustments and the design-chat notes join the same panel next; each already has a lifecycle, and joining is an entry in fsmes/services/review.py's registry — a reader and a reviewer — not a new mechanism.

Reading one before you sign it

A row on that panel says a draft exists. It says nothing about what the draft would do to the plant, and signing on that is signing blind.

Review on the row opens the substance, from GET /dashboard/pending-approvals/{kind}/{code}/{revision}:

What it shows Why it is there
The draft against the revision it would supersede A diff in the plant's own words — both values, never a summary of them
How many reasons the list holds, and would hold A list that grows by one and a list that shrinks by one are different acts
How many recorded intervals already carry the code And what the drafter said when they wrote it: a draft that waited a week was written against a smaller number
Who drafted it, and on whose behalf An agent drafting for a person says so here, not only in the audit trail
The revision it would supersede, whole With the one click that puts it back — undo is in the same reading as the change, not a hunt through a history screen

It is gated by the same capability that lists the kind. A panel that refuses the button while showing the substance is a panel that leaks the draft, so a caller who cannot sign a downtime reason cannot read its drafts here either.

Being walked through what changes

Walk me through it, on the review, hands the draft to the floor assistant — the same guide mode that walks an operator through recording an inspection, with the same ring around the real control on the real screen.

The walk is generated from the draft's own diff: one step per change, in order, and the approve button last with what undoing it would take said on the card. A draft that changes a name and a sentence is three steps. A retirement is two: what leaves the list, with the count of intervals that keep their label, and then the button.

The review of a waiting draft, with the walk on its first step: the ring
around the row that changed, and the coach card saying what the plant says
today and what the draft would make it say.

Nothing in it is narrated by a model. The steps are built from the two values themselves, deterministically, and the coach card says so — built from the draft itself, step by step; no model wrote this. What is about to be signed is the one thing in this product that may not be paraphrased (decision 0031).

The step goes to the floor, not to a description of the floor

A change to this vocabulary lands in one place: the dropdown an operator picks a stop from, at the machine. So that is where the walk takes you. A step whose change has a real control leaves the review and rings the actual <select> on the station screen, with the reason under review already chosen in it, so you read the change standing in front of the thing it changes.

The destination is computed from the plant's own records, never proposed by anything: the machine that has recorded the most stops under this code, with the coach card saying which machine and how many machines have recorded it at all. The vocabulary itself is plant-wide — every station offers the same list — so which station is a fact only the history can answer.

The first step of the same walk, on the station screen: PACK01's own reason
dropdown with the reason under review chosen in it, the ring around that
dropdown, and the coach card saying what the plant says today, what the draft
would make it say, and which machine this is being shown
on.

A step that changes the word rings the dropdown; a step that changes the sentence beside it rings that sentence, unless the plant has not written one yet — a ring around an empty paragraph points at nothing, so that step rings the word instead.

That screen opens the list to be read, not answered. Never mind and It is down are off the screen while it is open, and no state change is armed: an approver reading a dropdown must not be one mis-click from putting a running machine down. Close puts it away.

Three cases have nowhere honest to stand, and keep the old behaviour — the step stays on the review and describes the change in words:

Case Why there is no destination
A brand-new reason Nobody can have chosen a word the plant does not have yet, and the dropdown does not contain it, so ringing that dropdown would point at the absence of the thing under discussion
A code on the list that no machine has ever chosen Naming a machine out of the equipment table would be inventing a place
A code already retired It has left the list the walk would point at

The last step is always the Approve button, back on the review — so following the walk to the end returns you to the signature rather than stranding you at the machine. The coach card also carries a Back to the review button on any step that has left the dashboard, for stopping early, and the review you were reading is reopened when you get back. Nothing is signed by any of this: the draft is still waiting until Approve is pressed.

This is the first guide in the product that nobody authored, and the guide runner learned three things to play it: a step may point at one of several rows carrying the same anchor, by position (how many rows there are is not known until the draft is read); a step's destination may name which machine and not only which screen; and a guide may name the screen it came from, so a walk that crossed can be brought back. Everything else — the ring, the coach card, crossing screens, stopping — is what it already did.

Approving, and undoing

curl -sX POST "$MES/equipment/downtime-reasons/jam_infeed/approve/1" \
  -H "Authorization: Bearer $TOKEN"

The approved revision supersedes the one before it and the operator's dropdown changes at the next screen load. To undo, approve the previous revision. The revision that was in force becomes superseded; nothing is deleted and no history is rewritten.

Retiring a code

A vocabulary shrinks as well as grows. Retiring is a revision like any other, with "retires": true, and it is refused unless the draft says how many recorded intervals carry the code:

jam_infeed labels 412 recorded intervals. Say so in the draft
(`labels_intervals`) before retiring it: a code leaves the list with somebody
having looked at what it already labels, or it leaves it blind.

Because of the rule that makes this cheap:

Retiring a code changes what may be chosen next. It never changes what was chosen before.

An interval labelled jam_infeed keeps that label when jam_infeed leaves the list, and the pareto keeps showing it. A record is not un-made — the same principle decision 0029 applies to an order that over-ran. So the worst outcome of a vocabulary somebody regrets is a month of stops labelled with words they regret, which is exactly the situation a plant with a text box is already in.

On the floor

Once the plant has an approved vocabulary, going down takes a code from it. The station screen shows a select instead of the text box, and the API refuses a typed sentence with the reason why. That is deliberate: a list nobody has to use is a suggestion sitting beside the text box that caused the problem.

It is not a demand to invent something. The shipped starting vocabulary carries an explicit not yet determined — a stop labelled that way is counted, can be found again and can be re-labelled, which is more than a blank box ever gave anybody.

A plant that has approved nothing is unchanged: there is no list, so the text box is what it has and the text is what it gets.

Three of the four writers are untouched. A trigger composing "Infeed jam (Alarm_Word=1)", an inbound feed carrying the incumbent system's own reason column, and an agent tool all keep writing text, and the pareto says how much of the window came from each rather than pretending. (The OPC agent is a fifth thing and supplies no reason at all: every stop a machine reports by itself lands unlabelled. That is the cold start, and it is why the vocabulary has to come from somewhere other than the machines.)

Naming a stop after it is over, from the timeline

The station screen names a stop as it happens. A stop nobody was at is named afterwards, from the machine page's Timeline tab: click the block — or tab to it and press Enter — and a panel opens beside the bar with the records behind it. Where the reason is missing it says so in those words, nobody has said why yet, and offers the same approved list the station screen offers, over the block's own window.

It is the same write (POST /equipment/{code}/stops/label), so the same two rules hold: the code has to be on the approved list, and a label never overwrites a reason somebody already gave. The panel says how many records it named, because one drawn block can be several intervals folded together.

Naming a stop needs equipment.state. A reader without it is told that, and told which roles hold it, rather than shown a control that will refuse — the panel also names the person who labelled a stop and when, read out of the audit log by time, and says labelled here, by whom is not recorded where that record does not exist.

What the pareto says afterwards

GET /analysis/downtime groups by code where there is one and by text where there is not — the two never merge — and states both totals:

Field What it is
from_the_list_seconds downtime labelled with a code from the approved list
typed_seconds downtime labelled with text somebody typed
unlabelled_share downtime nothing labelled at all
vocabulary_total how many reasons are currently on the list

With the unlabelled share, the three account for every second of downtime in the window. One number alone would let a plant with six codes and a thousand typed sentences look like a plant with six reasons, and how much of this window came from the list is the number that says whether the vocabulary is being used.

What a plant starts with

A pack may carry masterdata/downtime_reasons.json (plant packs). The three lab packs and the cutlery demo ship six words — breakdown, changeover, micro stop, starved, blocked, and not yet determined — shaped after the ISO 22400 categories a plant will recognise. They are a starting vocabulary: the plant owns the list from its first edit, and fsmes pack apply never rewrites a code that is already there.

What this does not do yet

  • The clustering step. The assistant reading the last thirty days of what operators typed and proposing "these 40 spellings look like 6 reasons" is the follow-up. It needs a plant with a month of people typing; the lab plants are driven by the OPC agent, which supplies no reason, so they hold no typed history to cluster.
  • The coverage dry run that would report what a proposed list would have covered, for the same reason.
  • The analysis screen does not yet print the two totals above; the API carries them today.
  • Scrap has no reason column at all. That is a second pilot.

See also