Skip to content

resourceParamsObject

The resourceParams object describes properties for resource types, typically pre-declared via a define in the domain parameter file. Attribute names and types are links; the sub-object fields and the Helix extension block are documented below.

key string
Unique identifier for the resource type.
clone resourceParamsKey
Inherit values from another resource type.
livery string
Resource livery (visual identification / branding). Also a CONSTRAINT: a skill option can require particular liveries and a customer can permit only some — see Livery on the skillOption page.
speedFactor real default 1.0
Relative speed within the topography.
costKm real default 0.0
Cost per kilometre travelled.
costHour real default 0.0
Hourly operational cost.
overheads real default 0.0
Additional hourly cost while signed on.
productiveTravelFactor real default 0.0
Value attributed to travel that is considered productive.
cityBias real default 0.0
Area-preference bias toward or away from urban areas.
softWorkingLimit distance
Maximum distance from base (soft constraint).
hardWorkingLimit distance
Maximum distance from base (hard constraint).
atSensitivity distance
Resolution applied to the resource’s location (positional sensitivity).
idleDiscount real default 0
Discount for immediate deployment after sign-on.
reliableDiscount real default 0
Discount when placed after reliable stops.
calloutMargin real default 0
Penalty for the standby → call transition.
stopOnCaution real
Cost penalty for mandatory extended (stop-on) time.
voluntaryCaution real
Cost penalty for voluntary extended time.
deassignCost real
Cost penalty for deassigning a stop from this resource.
voluntaryDeassignCost real
Cost penalty for deassigning a voluntary stop.
deassignEarlyArrival timeInterval
Early-arrival allowance considered when deassigning.
minimumIncome real
Minimum income per hour the resource expects; weighs voluntary / standby work decisions.
stbyUsageFactor real
Weight affecting standby callout likelihood / cost during work hours.
stbyUsagePreWorkFactor real
Weight affecting standby callout likelihood / cost before work starts.
hostClockResolution timeInterval default 1 min
Resolution of roster-related stop times.
hostClockSpread timeInterval default 10 s
Spread applied to roster events.
stopOnMode IDLE | RELIABLE | ARVD | ONE | MAX
Mode for determining stop-on (extended-time) behaviour.
typicalStop stopParamsKey
Most common stop type attended (planning estimate).
typicalToStop stopParamsKey
Most common destination (to) stop type.
typicalLeadTime timeInterval
Typical idle time before the next work is available.
typicalTravelTime timeInterval
Typical travel time between stops.
typicalUsableTime timeInterval
Typical usable time at a stop.
leadFactor [dow: real]
Per-day-of-week multiplier on the typical lead time.
coreSkills [skill: null | real]
Core skills and proficiencies the resource provides.
exclusiveSkills [skill: null | real]
Skills exclusive to this resource (capacity / competition constraints).
secondaryActivity secondaryActivityObject
A secondary capability the resource can perform.
baseDeploy baseDeployObject
Controls base-deployment timing at the end of a shift.
surcharge surchargeObject
Additional costs for loaded vs unloaded travel.
planEarlyBase bool default true
Plan sign-off as early as possible.
alpha real default 0.0
Reserved / advanced — currently unused.
beta real default 0.0
Reserved / advanced — currently unused.
domain { [domain]: { [alert]: { travel, demand } } }
Spiral 2.4.0a surge factors for this resource type, keyed by surge domain then alert level (e.g. {"Road":{"AMBER":{"travel":2.0,"demand":2.0},"RED":{"travel":3.0,"demand":10.0}}}). Resource-side factors: travel (travel-cost scale) and demand (expected-demand scale). Activated for resources within a live surge zone naming this domain + alert — see surgeZoneObject.
helix helixObject
Helix-specific presentation configuration (see below).

The helix block is a Helix addition to the Spiral resourceParams (ignored by Spiral itself). It controls how the resource type is presented in the Helix client, plus server-side lifecycle hooks. onCreate / onDelete are "fnName()" specs resolved against the client crud module — see Backend hooks.

{
"icon": <string>,
"domain": <string> | null,
"label": spec | null,
"nextEvent": spec | null,
"nextIncrements": spec | null,
"rosterStop": { [NOW | REST | STBY]: helixObject } | null,
"onCreate": spec | null,
"onDelete": spec | null,
}
icon string
Name of the map-marker icon rendered for this resource type (resolves to a canvas-rendered icon in the client).
domain string
Helix-only — this resource type’s default skill domain (one of the names in helix.domains); sets the resource’s default domain and the group the skills editor opens on. Not sent to Spiral. Distinct from the top-level surge `domain` block above.
label spec
Expression producing the resource’s display label (default resourceLabel()), with `this` = the resource.
nextEvent spec
Expression producing the resource’s next roster event summary shown in the resource editor (default helixNextEvent()).
nextIncrements spec
Expression producing the time-increment choices offered when adjusting the next roster event (default helixNextIncrements()).
rosterStop { [NOW | REST | STBY]: helixObject }
Per-roster-state overrides of the presentation (helix block) applied to this resource type’s roster stops — deep-merged over the core roster-stop defaults. Also where the off-duty shift-state periods are authored (see below).
onCreate spec
Function called when a resource of this type is created live (after scheduling has started), with `this` = the resource.
onDelete spec
Function called when a resource of this type is deleted, with `this` = the resource being removed.

The first roster stop a resource shows in the list (the head of the roster chain — the one with no preceding stop) moves through time-driven periods relative to the resource’s shift start while it is waiting to sign on: std → dueOn → late → noShow. The shift start comes from the head stop itself, named by the roster block’s shiftStart field: the REST block authors shiftStart: 'fin' (the leave time — the rest ends and the shift begins), STBY authors shiftStart: 'arv' (standby begins on arrival). While on duty the shift start is the resource’s lastOn — which is also why the plan-window title is authored per-block: planTitle is helixNowPlanTitle() on NOW (start = lastOn), helixRestPlanTitle() on REST (start = the stop’s fin) and helixStbyPlanTitle() on STBY (start = the stop’s arv), so a domain can re-point any of them. Each period is a clientState the server computes (so it is consistent for every client and recorded in the audit trail); the client maps it to a colour and a display label exactly like any other per-state entry in the roster stop’s helix block.

Author the periods inside a rosterStop state’s helix block. A period is an ordinary per-state entry keyed by the canonical period id (std / dueOn / late / noShow), carrying the usual color and status, plus two period-only fields:

{
"at": <number>,
"on": spec | null,
}
at number
Band edge in seconds, in the shiftStart − now convention (positive = before the shift, negative = after): the period begins when (lastOn − now) drops below this. Ordered ascending; null marks the std catch-all (the earliest period, no boundary). Mirrors helixColorStart / delayColors band edges.
on spec
A then-expression (a crud-module function returning a field-object) fired ONCE as the period is crossed, IF the resource is still not signed on. Committed as a force at the resource. null (or absent) = no step — the period is display-only. Example: stripSkills() removes all of the resource’s skills so the optimiser stops assigning it work.

The colours and labels are yours to choose per period; a domain can also suppress an inherited period by setting its key to null, or add its own at/on. The core default (used by the demos) is std REST/light-grey → dueOn ----/white → late DUE/red → noShow LATE/black, with the noShow boundary firing stripSkills() as the worked example of the async on step. A client typically overrides the labels, colours and thresholds — and, where its own host system already reacts to a no-show, authors on: null so Helix does not double the step:

// resourceParams `default` — inherited by every type via clone
"rosterStop": {
"REST": { "helix": {
"std": { "color": "darkOrange", "status": "REST", "at": null },
"dueOn": { "color": "white", "status": "DUE", "at": 1200 },
"late": { "color": "red", "status": "LATE", "at": 0 },
"noShow": { "color": "black", "status": "NOSH", "at": -900, "on": null } // host system strips skills
}},
"STBY": { "helix": {
"std": { "color": "darkOrange", "status": "STBY", "at": null },
"dueOn": { "color": "white", "status": "STBY", "at": 1200 },
"late": null, "noShow": null, // a standby resource waits then arrives — no late/noShow
"ARVD": { "color": "lightBlue", "status": "STBY" } // Spiral’s own arrival state takes over
}}
}

The STBY shape above (std/dueOn only, late/noShow null) is now the CORE DEFAULT — a standby waits and then arrives, so nothing goes red and no skills are stripped. Nulling the post-boundary periods also ends the period walk at the shift start: from that instant the raw state colours show through (light blue on arrival, dark blue on a call) instead of the due-on band stretching past the boundary. A queued standby (one still behind a REST) doesn’t use these periods at all — its colour is inherited from the REST it follows (helixPriorRestColor() on the PLAN state), so the one visible pin carries the resting walk until the standby starts.

Because the colour keys off the period id and not the label, a period can keep a constant label while its colour changes (the STBY example stays STBY through darkOrange → white), and different domains can give the same period different labels (one domain’s DUE and another’s LATE can sit at the same threshold).

A period’s status is an expression, not just a literal — "status": "helixNextDuty(\"STBY\",\"DUE\")" labels the period by the upcoming duty, so a resource resting before a standby shift reads a constant STBY while the period colours walk, and the fallback label shows before an ordinary work shift.

Two refinements for special cases. In any colour band array (delayColors, helixColorEnd(...) and friends) the reserved colour name "null" — e.g. {"null": 3600} — is a leave-alone band: within it the band imposes no colour, so the state-driven colour keeps showing until the next state event. And the roster block’s shiftSpread (seconds, default 30, 0 = boundary-exact) controls how a fleet’s simultaneous period boundaries are spread so they do not all fire on the same second.

The on step, its still-not-signed-on gate, and the sim-time queue that drives the boundaries are the general timed-event mechanism — how an event is posted, gated and acted on is described under Timed events.

The standby callout (rosterStop.STBY.helix)

Section titled “The standby callout (rosterStop.STBY.helix)”

When Spiral plans work onto a standby resource it calls the resource out: the head STBY roster stop is emitted in state CALL (the only place CALL is valid — it is output-only, Helix never sends it), its fin truncated to the callout instant, with the planned work chained off it. Helix surfaces the call — dark blue by default, top of the resource list — and runs the response process from one config block on the STBY roster state:

"STBY": { "helix": {
"CALL": { "color": "darkBlue", "status": "CALL" }, // the called state's presentation
"messageOut": { "actionee": "resource", "urgency": 50, "text": true }, // a standby resource is messageable
"callout": {
"duty": null, // REQUIRED to enable the acceptance migration: the roster duty the standby
// becomes (pay rides with it). null = an external host owns the roster rewrite.
"response": "00:15:00", // acceptance → on-duty start (the callout response period)
"action": { "rank": 90, "actionee": "any", "text": "helixCalloutText()",
"outcome": ["accept", "refuse"] }, // text:null disables the whole Helix alert layer
"refusals": null // voluntary refusals before an automatic stand-down; null = record + alert only
}
}}
  • The alert is the indoor CHASE action (see builtInActions) synthesised from callout.action — default route the OWNING DESK, class-less; author a class to skill-route it, text: null to disable. The ENGINEER needs no action: their accept/refuse is state-driven (the CALL state presents the response screen; the answer travels as a resource update — ext.calloutResponse — the same door an external host uses). Raised while the stop is in CALL and cancelled the moment it leaves (a stand-down closes it with no further machinery). actionee: "any" reaches both the engineer’s inbox and the dispatcher’s queue. refuse (a reserved outcome name) is offered only when the call is voluntary.
  • Acceptance is a resource-document update, nothing else: the standby period in the roster becomes REST with callout.duty starting response after the acceptance instant, and the resource state flips to REST as the second indicator. Every door — the engineer app, the dispatcher, an external host posting the update itself — submits this same document (the SDK exports calloutAccept to build it), so an external response wins cleanly: the stop is re-derived, the open action closes, nothing double-fires.
  • CLLD is required call persistence: Helix sets the resource state STBY→CLLD on receipt of the CALL (prior to any acceptance), reverting to STBY if the call ends unanswered. Spiral takes no action on CLLD in normal running, but at startup re-seeds the CALL onto the STBY roster stop from it — without the marker an in-flight call would vanish across a Spiral restart. A host running its own callout channel stamps CLLD itself, as today; Helix’s mark is then a no-op.
  • Refusal records the outcome, counts it on the STBY stop, and excludes the resource from the called work (the chained stop’s input exclude), so a replan cannot call it straight back. When the domain sets refusals: N and the count reaches it, Helix emits the stand-down pattern — lock, deassign, resource back to standby — and tells dispatch; with null it only records and alerts.
  • The answer window is a contractual term, not process config: see contractParams…helix.callout.answer. Silence past the window is treated as a refusal (recorded as timeout, counted identically). No answer term ⇒ no Helix timer — the external host owns callout timing.
  • Channel notifications are one line: callout.notify: ["whatsapp","sms"] names the notification channels rung when the call goes up (and told to stand down if it ends unanswered). Per-resource addressing lives on the resource record — ext.notify: { "whatsapp": "+44…", "sms": "+44…" }. notify: null (the default) sends nothing.
  • Under the hood these ride the roster stop’s onState hooks, wired by core with wildcardsonState["*"]["CALL"] → notifyCallout() and onState["CALL"]["*"] → notifyCalloutEnd() ('*' matches any from/to state; exact cells win, then the '*' row, then the '*' column — needed because the off-duty periods make the from-state std/dueOn rather than ARVD). A domain can override either function, or author its own hook cells (e.g. "onState": { "*": { "CALL": "sendCalloutSms()" } }) implemented in the client module — see notification channel adapters.