Skip to content

How Manifest Settings Drive Busroot

The Schema Reference says what each field is. This page says what it does: how each part of the configuration shapes what Busroot records, calculates and shows. It is worth reading before changing configuration on a live account, because several settings have effects that are not obvious from their names.

Time and calendar

A plant's timezone, dayStartHour and weekStartDay anchor every time-based calculation for its stations:

  • Every date range resolves in the plant's timezone. "Today", a named day, a week, a month, a quarter, a year: all are interpreted where the plant is, not where the server or the viewer is.
  • dayStartHour moves the day boundary. A factory whose day starts at 06:00 means "Tuesday" runs from 06:00 Tuesday to 06:00 Wednesday, in every report and dashboard. "Today" and "Yesterday" respect the same offset.
  • weekStartDay moves the week boundary, and with it the boundaries of quarters and years, which are built from weeks. Weekly and longer views for a plant that starts its week on Sunday genuinely start on Sunday.

Change these on an established plant with care: the boundaries of historical reporting periods move with them.

Shift patterns and OEE

Shifts decide when time counts. Production counts only need a running schedule (below), but utilisation, OEE and the station's live running/stopped state also need the time to fall inside a shift (see In-shift time):

  • Outside shift hours a station reads out of shift and accrues no productive time, however much it is producing. Counts still record; the percentages don't move.
  • A station resolves its shift pattern from its own shiftPatternCode, or its group's if it doesn't set one. A station with no resolvable pattern is never in shift. It will show production counts and permanently zero utilisation. This is the most common surprise when commissioning a new station: if the numbers are all zero except the count, check the shift pattern first.
  • This is why a 24/7 shift pattern is the practical choice while commissioning: a test at any hour still produces figures. Realistic patterns can come later.

Station codes are permanent

A station's code is the key that ties everything recorded (production, downtime, utilisation, energy) to the station. Two consequences:

  • Renaming a code orphans the station's history. The data recorded under the old code is not deleted, but nothing will ever read it back. Treat a code change as decommissioning one station and creating another; change name for display instead.
  • Incoming device messages must use the station code exactly as it is spelled in the manifest. Signals are stored under the code as published on the MQTT topic, so a device publishing to station1 does not land data on a station whose code is STATION1.

Archived stations keep their history, and creating a new station with an archived station's code (case-insensitively) is rejected, so the historical data can never be silently claimed by a new station.

Station behaviour settings

These settings decide how raw device signals become "running", "down" and performance figures:

  • Running is decided by productiveStatusMode. With the default productive_signal, the station is productive while the device sends productive signals, each one holding the state for productiveHoldOnTime (so a machine that signals once per cycle stays "running" between cycles. Set the hold-on to comfortably exceed the gap between signals). With electrical_usage or line_speed the station is running while the measured value is above the corresponding stopped-threshold. Production counts alone never make a station productive. A device that publishes only counts shows output and 0% utilisation until it also publishes a productive signal, or the mode is switched.
  • Down is decided by downtimeDetectionMode. In the common utilisation mode, a stop becomes a recorded downtime once it lasts longer than utilisationDowntimeThreshold (default 1 hour, usually worth lowering). In production mode the threshold derives from the SKU's cycle time and downtimeDurationThresholdMultiplier. off records no downtimes at all.
  • Speed signals pass through speedScale and speedOffset before display, so a sensor whose raw value doesn't match the real speed can be calibrated in configuration rather than in the device. speedToProductionRatio additionally converts speed into production counts for continuous processes that have no discrete unit signal.
  • Performance targets come from the running SKU's rate (cycleTime, unitsPerMinute or unitsPerHour) when it has one, falling back to the station's own rate fields. lineSpeedTarget switches the performance comparison to speed-vs-target.
  • Costs use the SKU's value per unit where set, with the station's idleCostPerMinute covering losses that can't be attributed to a SKU. See Downtime cost and Lost production cost. electricalPowerNominalKw is used for Power vs nominal.

Configuration vs runtime state

A handful of station fields (statusCode, statusCodeUpdatedAt, currentOperatorInitials, alertMessage, alertExpiresAt) are state Busroot writes, not configuration you author. They appear in the schema so the document is complete, but the live values are maintained by the platform as devices report and operators sign in and out. Setting them in a manifest achieves nothing lasting.

SKUs

  • A schedule may only name a SKU that already exists. Creating a schedule with an unknown SKU code is rejected. Nothing creates the SKU for you. Add the SKU first, either in the admin UI or in a version-controlled manifest; keeping a permanent test SKU (e.g. CALIBRATION) in the list is normal practice.
  • value drives money. It is the unit value used by Lost production cost, Quality cost and Downtime cost.
  • Rates drive targets. The SKU's cycleTime/unitsPerMinute/unitsPerHour set the Target count and the Performance comparison while a schedule for the SKU runs, and project schedule durations on the Gantt.
  • stationCodes / stationGroupCodes restrict scheduling. When set, only the named stations (or stations in the named groups) can be scheduled with the SKU.
  • allowUseInQuickSchedule controls whether operators can pick the SKU when starting an ad-hoc schedule from the Operator Interface.

Reasons and the include/exclude cascade

A reason's type list decides where it is offered: downtime reasons when categorising stoppages, non-production reasons when scheduling planned non-production time, quality reasons when recording rejected units.

Which reasons a particular station offers is decided by the includeReasonCodes/excludeReasonCodes lists, evaluated level by level: account, then plant, then station group, then station:

  • No include list anywhere → every reason is available.
  • Any include list → the available set starts from the include lists: each level's includes are added (a lower level can re-add something a parent excluded).
  • Each level's excludes are then subtracted at that level, after its includes.

The same cascade controls which Task lists apply to a station, via includeTaskListCodes/excludeTaskListCodes, with one deliberate difference in the first rule:

  • No include list anywhere → no Task list is available. A list has to be included explicitly at some level before its Tasks reach any station, so creating a Task can never put it on every Operator Interface in the company by accident.

The legacy includeTaskCodes/excludeTaskCodes are no longer read at any level.

requireNotes on a reason works differently: it inherits down the reason tree, not the station hierarchy: a child reason that doesn't set it takes the nearest ancestor reason's setting, and false explicitly opts out. When it resolves on, operators must enter a note before the reason can be confirmed against a downtime.

Tasks

  • Trigger is a property of the Task list, so every Task in a list shares it. A Task's own trigger is legacy and no longer read.
  • A list trigger of schedule means its Tasks must be completed once per production schedule. They become pending shortly after a schedule starts and are missed if the schedule ends without them.
  • A list trigger of cron means its Tasks are pending at every cron fire, evaluated in the plant's timezone; the fire is the deadline. availableBeforeDeadlineMinutes controls how long before the deadline the Tasks become pending and appear in the Operator Interface, so a weekly check due Friday 17:00 can be made available only from Friday morning; a Task not completed by the fire is missed.
  • A list trigger of on_demand (On Demand) means its Tasks have no deadline: the operator starts the list in the Operator Interface (POST /api/task-list/start?code=<list code> with the stationCode), which makes every Task in it pending, completes them one by one, and can start it again straight away. They are never missed for time (only when the Task, list or station assignment is removed while a run is open), and count as pending from when the list is started until each is completed.
  • Any list can be started from the Operator Interface, whatever its trigger. The run is an On Demand instance: no deadline, recorded as On Demand. The scheduled instance still becomes pending on its own; if that happens while the manual run is open, the run becomes the scheduled instance and is missed like any other if it is not completed in time.
  • A single Task can be started through the API (POST /api/task/start?code=<task code> with the stationCode and taskListCode), which is the same as starting its list for that Task alone: an On Demand instance in that list only. The list is required because a Task in two lists is a separate instance in each. The Operator Interface only starts whole lists.
  • Older manifests may still carry frequency / always; they are read as trigger / on_demand and rewritten on the next save.
  • requireNotes stays on the Task, not the list: it is about what the operator records, not about timing. The note is kept with the completion and shown in Task History.
  • Which stations a Task applies to follows the Task list include/exclude cascade above. A SKU's taskCodes still name Tasks; SKU-level Task scoping has been removed from the schema entirely.
  • Completion is per (Task, Task list, station). A Task in two lists gets its own rows and its own completions in each, so completing it in one list leaves the other outstanding, and it counts twice towards a station's pending-task count while both are open.
  • Each Task carries an order within each of its lists, which is the order it appears in in the Operator Interface and in the Task admin table.
  • The Task instance is a row, completed by id. A row is created when the Task becomes pending (cron: when its availability window opens; per schedule: shortly after the schedule starts; any list: when it is started from the Operator Interface) and appears on the station's requiredTasks as taskCompletionId; POST /api/task-completion/{id}/complete completes it. Every Task in every list assigned to the station is on requiredTasks at all times; taskCompletionId is null while it is not pending. Task History records every instance as completed, missed or pending, so the audit trail exists whether or not operators tick things off. Pending has one meaning everywhere: a row that is neither completed nor missed. It is what every pending-task indicator counts.

Energy totals

By default a plant's or group's energy (kWh, cost, CO2) is the sum over its member stations. totalEnergyStationCodes replaces that: when set, the level's energy is the designated stations' energy instead: the physical meter's reading rather than the sum of sub-meters.

The designated stations are usually meter-only stations (enableMeterOnly), which exist to carry a physical meter's electrical signals and are hidden from production views. Group designations apply first, then the plant's own, so a plant total can legitimately differ from the sum of the group rows on screen, because it is a different physical measurement. Energy CSV exports include the meter stations as rows so the totals can always be reconciled.

Maximum production count per schedule

maxProductionCountPerSchedule (Account Settings → Formatting & Units) is a safety net against a grossly wrong count, not a target. It exists because a single mistyped parts-per-cycle value can turn a normal shift into six figures of production: set it a little above the largest schedule you would ever expect, and a schedule stops accumulating there instead.

  • The limit is on units (after parts-per-cycle is applied, good plus bad), which is the number shown in the Operator Interface and in the Cockpit. It is per schedule, and a schedule belongs to one station.
  • It applies to every way production is recorded: cycles from the machine simply stop being recorded, and a manual entry or a correction that would take the schedule past the limit is rejected. A negative correction is always allowed, so a schedule can be brought back down.
  • Once a schedule has reached the limit, its count is displayed with (limit) after it, for example 2,000 (limit) / 500. There is no other warning; the count itself says recording stopped.
  • Setting the limit applies it immediately to every schedule, including ones already running. Nothing already recorded is trimmed, so a schedule that is already above the limit keeps its count and simply records nothing more.
  • It also bounds the parts-per-cycle a schedule will run at. A PPC above the limit could never be produced (the schedule would be capped on its very first cycle), so it is refused at entry rather than accepted and then silently capped. The bound applies to the final PPC whatever its source, and the message names that source, because the field to go and change differs:

    Source Message
    The schedule's own partsPerCycle (schedule form, API, Upload Schedule) and the Parts Per Cycle field on Ad Hoc Production "PPC override exceeds the limit of N units per schedule"
    The PPC the schedule inherits from its SKU when it sets none of its own "This SKU's PPC exceeds the limit of N units per schedule"
    partsPerCycleOverride: the Operator Interface's 'Set Parts per Cycle' screen and the API "PPC exceeds the limit of N units per schedule"
    - The SKU's own Parts per Cycle field is not validated against the limit, so existing SKUs never
    have to be edited when a limit is set. The check happens where the SKU is applied to a schedule
    instead: scheduling a SKU whose PPC is above the limit is refused, and the planner has to enter a
    lower Parts per Cycle on the schedule or pick a different SKU. The Gantt's Add Schedule dialog has
    no Parts per Cycle field of its own, so there the message is shown on the SKU field (the only
    thing on that form that can be changed to clear it) as soon as the SKU is picked.
    - Only the schedule being saved is checked. Schedules created before the limit was set are left
    alone, and editing one (moving its start time, say) does not start failing because of a PPC it
    already carries.
    - Because production is processed in ~30 second cycles, a schedule can settle a few units above the
    limit. Leave the setting empty for no limit. That is the default, and how every account behaves
    until it is set.
    - Continuous schedules (no planned quantity) are capped too, and some run for months. Either set
    the limit high enough for them or leave it empty.
    - A capped schedule does not raise a stop. On stations using Production Cycle Monitoring a stop is
    inferred from a gap between recorded units, and a schedule that has reached the limit records
    nothing at all. So the period from the limit being reached to the end of the schedule is excluded
    from stop detection, the same way a non-production period is. Detection resumes when the schedule
    ends. Stations using Utilisation Monitoring are unaffected either way: they judge productivity from
    the machine's own signal, not from recorded production.

Feature flags in practice

The flag table lists what each flag does; the cascade is what makes them practical at scale. Two worked examples:

  • Enable manual downtime for one plant only. Leave enableManualDowntime unset (off) at the account, set "on" on the plant. Every group and station in that plant inherits on; the rest of the account stays off.
  • Turn a flag on everywhere except one station. Set "on" at the account, and "off" on the one station. The station's own value is the nearest defined one, so it wins over the account's.

Remember the default: a flag nothing sets is off. When something seems missing on a new account, check whether its flag was ever turned on at any level.