Overview
A Schedule is what anchors a simulation to real-world timing. Without a schedule, a model runs in abstract time with synthetic arrivals, useful for exploratory work, but not for comparing against your actual floor. With a schedule, the simulation is anchored to real dates, real shift patterns, and real production plans. Material enters when your plan says it does, shift transitions fire at specific wall-clock times, and results are comparable directly to what your floor produced. Every serious use of ProDex involves at least one schedule. Most models have several: one for the “normal” plan, a few for alternative scenarios you’re comparing. If you’re orienting yourself in the simulation workflow first, start with the Simulation overview — this page is the deep reference for the timing layer. The mental model that makes schedules click is the two-plane split: the schedule carries when things change (releases fire, shifts turn over), while the model carries what each component does at each change. A 30-day, three-shift schedule touching five resources needs ~90 scheduled emits plus 15 event listeners defined once in the model — not 450 individual capacity entries. Schedules live alongside Snapshots as the two things an Experiment comparison row pulls together. A Snapshot freezes the model + schedule together (the schedule is embedded in the snapshot, not referenced); the schedule active when the snapshot was captured travels with it.Choosing a Schedule
The schedule selector lives in the Modeler’s top bar. The dropdown lists every schedule defined on the model, plus:Default (no schedule): when selected, the model runs in abstract time with whatever stochastic arrivals the Sources define. There’s no schedule object behind this: it’s a null selection.+ New schedule: creates a fresh, empty schedule and switches to it.Edit: opens the Schedule Editor modal for the currently selected schedule. Lives inside the dropdown menu (not next to the selector).Delete: removes the currently selected schedule. Opens a confirmation dialog: “Delete Schedule: Are you sure you want to delete ‘{name}’? This action cannot be undone.”
The Schedule Editor
The Schedule Editor is a modal titled Edit {schedule name} that takes over the working area while you author a schedule’s timing and events. The top of the modal has four fields that define the schedule:- Name: short, descriptive. This is what shows in the top-bar selector.
- Description: optional free text explaining what this schedule represents.
- Start Time: the wall-clock moment simulation begins. Every event timestamp on this schedule must fall at or after Start Time.
- End Time: the wall-clock moment simulation ends. If left blank, the simulation runs for the model’s configured Duration starting from Start Time. If set, End Time overrides Duration.
name, description, start_time, end_time) plus two arrays that hold everything else:
material_releases[]: entities entering the model at specific timesscheduled_actions[]: assignments and emissions fired at specific times, optionally gated by a condition
actions[] array where each action is either an assign or an emit. The two action types share a parent scheduled_actions[] entry and inherit its release_time and optional condition.
A single + button in the modal’s top-right opens a picker for which kind of event to add: material release, assign-variable, or emit-event.
What a Schedule Contains
A schedule is a sequence of material releases and scheduled actions, each stamped with a specific absolute wall-clock time. The simulation runs from Start Time to End Time (or for the model’s configured Duration if End Time isn’t set), and each event fires when wall-clock simulation time reaches its timestamp. Every event timestamp is absolute, not a relative offset from Start Time.Material Releases
A material release tells a Source or Buffer to emit one or more entities at a specific wall-clock time. The form has these schema fields:element_id: the id of a Source or Buffer to release from. Supports hierarchical addressing (model_node_1::source_1) for components inside a Model Node. Sources accept releases whose entity type matches theirentity_type; Buffers are stricter — they accept only their exact entity type. An incompatible pairing fails validation.entity_type_id: the slug of the entity type to release.quantity: a positive integer (exclusiveMinimum: 0, integer-typed; no DSL union).release_time: absolute wall-clock moment the release fires (ISO datetime, written as UTC withZ). A release timestamped exactly at the schedule’s Start Time seeds initial WIP — material already on the floor when the run begins.attributes: values to set on the newly created entities using the six assignment strategies: Fixed, DSL Expression, Round Robin, Random Choice, Random, Weighted. A DSL Expression here runs in no-entity context — it can read constants, lookups, and state variables, but there’s no entity in scope yet.
priority="rush" and due_priority=5; at Nov 1, 2024 9:00 AM UTC release 30 units of Product B; and so on.
A Source’s arrival logic and material releases run in parallel. They aren’t mutually exclusive. A Source can keep producing stochastic arrivals while a schedule simultaneously injects specific batches at scheduled times, and event-driven
release_entity actions can fire on top of both. If you want a schedule-only Source, leave its arrival logic empty.Scheduled Actions
A scheduled action fires at a specific wall-clock time without being tied to any component’s lifecycle event. Schema fields:time: absolute wall-clock moment the action fires. (Note the field name —release_timebelongs to material releases only.)condition(optional): boolean DSL expression. The action only fires if the condition evaluates true at that time.actions[]: list ofassignoremitentries that fire together at this time.
assign: write to a State Variable.emit: publish to a Topic.
- A scheduled
emitfires at the shift boundary: for example, publish topicshift_start_night. - An Event Listener on the resource subscribes to that topic and sets capacity to the night value.
Scheduled actions run in no-entity, no-component context.
SELF is not available (it’s restricted to event hooks and event listeners), and there’s no entity in scope. Expressions here can reach state variables, constants, lookup tables, simulation state (SIM_TIME, SIM_DURATION), and component query functions with explicit component ids.Recurring Actions
Shift boundaries repeat, and you shouldn’t have to author 90 identical emits by hand. The schedule builder’s repeated scheduled action expands a recurrence into flat schedule entries at authoring time:every accepts positive whole-number durations with s/m/h/d units, and an optional when adds a condition to every generated entry. The expansion happens when the schedule is written — the saved schedule contains ordinary scheduled_actions[] rows, so nothing about the run-time behavior changes. Ask Dexter for a shift calendar and this is how it builds one.
Hierarchical Addressing
element_id accepts the :: separator for components inside a Model Node. Examples:
source_1: a top-level Sourcemodel_node_1::source_1: a Source inside a Model Nodeouter_node::inner_node::buffer_1: deeply nested
:: separator is reserved, so no single component id may contain it.
The Shift Scheduling Pattern
Most factories have shift-based capacity that changes through the day and week. The idiomatic way to model this in ProDex combines schedules, topics, state variables, and event listeners:- A shift state variable:
CURRENT_SHIFT, typetext, initial value"day". - Scheduled
emitactions at shift boundaries: publish topicshift_dayat each day-shift start, publish topicshift_nightat each night-shift start, each stamped with its specific wall-clockrelease_time. - An Event Listener on each affected resource subscribing to the shift topics: when
shift_nightis published, set CNC machine capacity to the night value and updateCURRENT_SHIFTto"night". - Expressions reference
CURRENT_SHIFT: arrival rates, priority scoring, and anything else that varies by shift reads the state variable and branches accordingly.
Multiple Schedules per Model
A model can have multiple schedules defined simultaneously. Each is a complete set of material releases and scheduled actions with its own Start Time and End Time; you pick which one to use from the top-bar selector when running a simulation, and any Snapshot saved while a schedule is active embeds that schedule for downstream runs. Typical uses:- Baseline vs. stress scenarios: a “normal” schedule and a “demand surge” schedule, compared in the same experiment.
- Historical replay: last month’s actual demand as one schedule, a planned demand forecast as another.
- Shift variations: a standard five-day schedule and an alternative seven-day weekend-coverage schedule.
Start Time and End Time
- Start Time is the wall-clock moment simulation begins. Every material release and scheduled action timestamp must fall at or after Start Time.
- End Time, if provided, is when simulation ends — it overrides the model’s configured duration. Useful for running the same schedule against a shorter or longer window without editing the schedule’s events.
- If End Time is left blank, the simulation runs for the model’s configured Duration from Start Time.
- Events timestamped after End Time (or after Start Time + Duration if no End Time is set) fall outside the run window and are never reached.
Z) and shift boundaries stay exactly where you put them.
Authoring Paths
There are three ways a schedule comes to exist, all producing the same file:- The Schedule Editor — the modal described above, best for small edits and review.
- Dexter — the fastest path for real data. Upload the raw material (a CSV of orders, a shift calendar, an ERP extract) and ask Dexter to build the schedule; it profiles the data and generates the releases and actions for you. This is how you turn an operational file into a schedule without hand-authoring each release.
- The Python builders — what Dexter uses underneath:
sim.schedule(name, start=..., end=..., material_releases=[...], scheduled_actions=[...])withsim.material_release(element, entity, quantity, at=...),sim.scheduled_action(...), andsim.repeated_scheduled_action(...).
models/{model-slug}/schedules/{slug}.json — the parent model must exist first, and a schedule can’t be attached to a different model. The currently selected schedule also rides along as context when you chat with Dexter, so “add a Saturday shift to this schedule” resolves without naming it.
Schedules in Results Data
Every run against a schedule materializes aschedule_definition table in the results dataframes, one row per atomic entry:
- A scheduled action with N inner actions expands to N rows, discriminated by
action_index; material releases are one row each. entry_typeis one ofmaterial_release,scheduled_assign, orscheduled_emit.- Columns are typed per entry: releases carry
element_id,entity_type_id,quantity, andattributes_json(the JSON-encoded assignment dict — populated only on releases with per-release overrides); scheduled assigns carryvariable_nameandvalue_expr; scheduled emits carrytopic.condition_expr(the unevaluated DSL condition, null when unconditional) applies to actions only, andaction_indexis null on release rows. - Every entry is registered up front, so a condition-gated action appears in the table even if its condition never evaluates true at runtime — the row records what the plan declared, not what fired.
Tips
- Every serious model needs a schedule. Synthetic arrivals are fine for the first pass; real schedules are required for comparable results.
- Use absolute wall-clock timestamps consistently. It’s tempting to think in relative offsets (“release at +3600s”) but every event needs an absolute timestamp. If you’re sketching out a schedule on paper, compute the absolute timestamps from your base Start Time before entering them.
- Use
conditionon scheduled actions to gate behavior on runtime state, e.g., only emit a topic if a state variable is in a particular mode. - Keep schedule-level logic in the schedule, component-level logic in Event Listeners. The schedule emits the signal; the listener handles the side effects.
- Use multiple schedules for experiments. Shift patterns, seasonal demand, what-if load cases, capacity planning under different assumptions — schedules are one axis of variation, model configuration the other.
- Ordering of simultaneous events is not specified — don’t rely on it. If ordering matters between two events at the same timestamp, stagger them by a second or chain them through a topic.
- Stamp provenance on planning numbers. Release quantities, weighted-assignment weights (which accept DSL expressions, not just numbers), and action conditions all take
derived/stated/assumedstamps — an assumptions review will surface any unstamped guesses in the plan.

