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.
-
keystring - Unique identifier for the resource type.
-
cloneresourceParamsKey - Inherit values from another resource type.
-
liverystring - 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.
-
speedFactorrealdefault1.0 - Relative speed within the topography.
-
costKmrealdefault0.0 - Cost per kilometre travelled.
-
costHourrealdefault0.0 - Hourly operational cost.
-
overheadsrealdefault0.0 - Additional hourly cost while signed on.
-
productiveTravelFactorrealdefault0.0 - Value attributed to travel that is considered productive.
-
cityBiasrealdefault0.0 - Area-preference bias toward or away from urban areas.
-
softWorkingLimitdistance - Maximum distance from base (soft constraint).
-
hardWorkingLimitdistance - Maximum distance from base (hard constraint).
-
atSensitivitydistance - Resolution applied to the resource’s location (positional sensitivity).
-
idleDiscountrealdefault0 - Discount for immediate deployment after sign-on.
-
reliableDiscountrealdefault0 - Discount when placed after reliable stops.
-
calloutMarginrealdefault0 - Penalty for the standby → call transition.
-
stopOnCautionreal - Cost penalty for mandatory extended (stop-on) time.
-
voluntaryCautionreal - Cost penalty for voluntary extended time.
-
deassignCostreal - Cost penalty for deassigning a stop from this resource.
-
voluntaryDeassignCostreal - Cost penalty for deassigning a voluntary stop.
-
deassignEarlyArrivaltimeInterval - Early-arrival allowance considered when deassigning.
-
minimumIncomereal - Minimum income per hour the resource expects; weighs voluntary / standby work decisions.
-
stbyUsageFactorreal - Weight affecting standby callout likelihood / cost during work hours.
-
stbyUsagePreWorkFactorreal - Weight affecting standby callout likelihood / cost before work starts.
-
hostClockResolutiontimeIntervaldefault1 min - Resolution of roster-related stop times.
-
hostClockSpreadtimeIntervaldefault10 s - Spread applied to roster events.
-
stopOnModeIDLE | RELIABLE | ARVD | ONE | MAX - Mode for determining stop-on (extended-time) behaviour.
-
typicalStopstopParamsKey - Most common stop type attended (planning estimate).
-
typicalToStopstopParamsKey - Most common destination (to) stop type.
-
typicalLeadTimetimeInterval - Typical idle time before the next work is available.
-
typicalTravelTimetimeInterval - Typical travel time between stops.
-
typicalUsableTimetimeInterval - 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).
-
secondaryActivitysecondaryActivityObject - A secondary capability the resource can perform.
-
baseDeploybaseDeployObject - Controls base-deployment timing at the end of a shift.
-
surchargesurchargeObject - Additional costs for loaded vs unloaded travel.
-
planEarlyBasebooldefaulttrue - Plan sign-off as early as possible.
-
alpharealdefault0.0 - Reserved / advanced — currently unused.
-
betarealdefault0.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.
-
helixhelixObject - Helix-specific presentation configuration (see below).
Helix extensions
Section titled “Helix extensions”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.
-
iconstring - Name of the map-marker icon rendered for this resource type (resolves to a canvas-rendered icon in the client).
-
domainstring - 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.
-
labelspec - Expression producing the resource’s display label (default resourceLabel()), with `this` = the resource.
-
nextEventspec - Expression producing the resource’s next roster event summary shown in the resource editor (default helixNextEvent()).
-
nextIncrementsspec - 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).
-
onCreatespec - Function called when a resource of this type is created live (after scheduling has started), with `this` = the resource.
-
onDeletespec - Function called when a resource of this type is deleted, with `this` = the resource being removed.
Off-duty shift-state
Section titled “Off-duty shift-state”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:
-
atnumber - 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.
-
onspec - 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 aclassto skill-route it,text: nullto 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
rosterbecomes REST withcallout.dutystartingresponseafter the acceptance instant, and the resourcestateflips toRESTas the second indicator. Every door — the engineer app, the dispatcher, an external host posting the update itself — submits this same document (the SDK exportscalloutAcceptto 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→
CLLDon receipt of the CALL (prior to any acceptance), reverting toSTBYif 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 setsrefusals: Nand the count reaches it, Helix emits the stand-down pattern — lock, deassign, resource back to standby — and tells dispatch; withnullit 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 astimeout, counted identically). Noanswerterm ⇒ 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
onStatehooks, wired by core with wildcards —onState["*"]["CALL"] → notifyCallout()andonState["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-statestd/dueOnrather 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.