Workflows v0.5¶
A workflow connects the steps of an analysis. The steps run as one unit, in the same way each time. A usual sequence is: do a check of the data, decide what to do, prepare the data, choose settings, fit, and test against benchmarks. Then select the best model, do the governance checks and write a report. Each step is a node. The arrows between the nodes carry typed results.
A workflow is a document (kind: Workflow, YAML or JSON). It has versions, the same as a
spec. The platform validates it before it runs. A workflow can run on a schedule, or each
time that a dataset gets a new version.
Status
The contracts are in mapi_spec.workflow, mapi_spec.workflow_nodes,
mapi_spec.workflow_templates, mapi_spec.expr, mapi_spec.splitplan and
mapi_spec.stattests. The engine and the node implementations are in mapi_workflows.
The endpoints are in the HTTP API reference. The
workflows guide gives the practical walkthrough and cookbook.
The canonical example¶
Most forecasting projects need the template ts_readiness_arimax:
flowchart LR
D[(data)] --> ADF[ADF] & KPSS[KPSS] & PP[PP] & ZA[Zivot-Andrews] & S[Seasonal strength]
ADF & KPSS & PP & ZA --> W{Difference?}
S --> SS{Seasonal difference?}
SS -- then --> SD[Seasonal difference]
W -- then --> DF[Difference]
SD --> DF
DF --> ACF[ACF/PACF]
D --> O[AR/MA order search]
D --> F[Expanding-window folds]
O & F --> BT[ARIMAX backtest]
F --> N[Naive benchmarks]
BT & N --> C[Compare] --> SEL[Select] --> G[Governance] --> R[Report]
- Unit-root tests. ADF and Phillips-Perron ask "does the series wander?". KPSS asks the opposite question. Zivot-Andrews permits one sudden break.
- Decisions. One branch differences the series only if the tests show that it wanders. A different branch seasonally differences the series only if the season is strong.
- Orders. ACF/PACF shows how many past values are important. An information-criterion grid search selects the AR and MA orders. It gets the differencing decision as an input.
- A correct backtest. An expanding-window plan refits ARIMAX on each fold. The plan has an optional gap (for late data) and some horizons (1, 4 and 8 weeks ahead).
- Benchmarks. The same folds score naive and seasonal-naive forecasts. The comparison and the selection keep ARIMAX only if it is better than the benchmarks.
- Governance and report. The selected model goes through the policy checks. A plain-language report explains each decision.
The document¶
apiVersion: mapi/v1
kind: Workflow
metadata: {name: ts-readiness, project: growth}
params:
alpha: {type: number, default: 0.05}
season: {type: integer, default: 52}
inputs:
data: {type: dataset}
nodes:
- {id: adf, use: test.adf, inputs: {data: inputs.data}, with: {alpha: {$param: alpha}}}
- {id: kpss, use: test.kpss, inputs: {data: inputs.data}}
- id: wanders
use: branch.if
with: {condition: "not nodes.adf.reject or nodes.kpss.reject"}
- {id: diff, use: transform.difference, inputs: {data: inputs.data}, when: wanders.then}
- id: order
use: search.arma_order
inputs: {data: inputs.data}
with: {d: {$expr: "if(nodes.wanders.value, 1, 0)"}, max_p: 3, max_q: 3}
outputs: {orders: order.table}
| Field | Meaning |
|---|---|
params |
Run-time parameters with a type, a default (or required: true), bounds and permitted values. A run can override them. |
inputs |
The items that the workflow uses: dataset, model_version, split_plan... with an optional default reference |
nodes[].use |
The node implementation (below), optionally with a version range |
nodes[].inputs |
Port bindings: inputs.<name> or <node>.<port>. For a single-value port, a list gives alternatives. The port uses the first alternative whose node ran. For a multiple port (compare.models candidates, report items), a list collects all sources that ran. |
nodes[].with |
Parameters. The platform checks them against the JSON Schema of the node. A value can be {$param: name} (a workflow parameter) or {$expr: "..."} (calculated from upstream results). |
nodes[].when |
<branch>.then or <branch>.else. The node runs only on that arm. |
nodes[].retries, timeout, cache, on_failure |
Execution settings for each node. With on_failure: continue, the platform treats a failure as a skip. |
outputs |
Named results of the workflow (<node>.<port>). A parent workflow reads these results from a sub-workflow. |
If a node is gated by an arm that the workflow did not take, the node is skipped. All
nodes that need that node are also skipped, unless their input has an alternative. This is
how "difference only if necessary" goes into the next step: data: [diff.data, inputs.data].
Typed ports¶
Each edge carries one of these types: dataset, test_result, split_plan,
model_version, metrics, table, plot, report or evidence
(mapi_spec.workflow_nodes.PortType). If you bind a test_result to a dataset port, you
get a validation error before a node runs.
Conditions: a safe expression language¶
Branch conditions and $expr values use a very small language (mapi_spec.expr). A
hand-written parser parses it, and the platform evaluates it over JSON. The platform
never gives it to the Python eval. It never reads an attribute of a Python object. All
inputs have limits: 2000 characters, 400 tokens, nesting depth 32 and 10 000 evaluation steps.
nodes.adf.p_value < params.alpha and not nodes.kpss.reject
nodes.seas.strength >= 0.64 or 'seasonal' in params.flags
[nodes.order.best_p, if(nodes.wanders.value, 1, 0), nodes.order.best_q]
coalesce(nodes.search.best_score, 0) < 500 and exists(nodes.za.details.break_at)
- Values: numbers,
'text'or"text",true,false,null, and lists[a, b]. - References:
nodes.<id>.<field>(the result of a node),params.<name>,inputs.<name>andrun.<field>. Index with[0]or['5%']. - Operators:
== != < <= > >=,in,not in,and,or,not,+ - * / %, unary-and parentheses. You cannot chain comparisons. The language refusesa < b < c. Writea < b and b < c. - Functions:
abs,min,max,len,round,any,all,exists,coalesceandif(condition, a, b). Theiffunction evaluates only the selected value.
A condition can also read the results of a Bayesian fit. These examples read the results of
model.summary (node s) and evaluate.ppc (node ppc):
coalesce(nodes.s.max_rhat, 99) < 1.01 and nodes.s.min_ess >= 400
nodes.s.params['beta[north]'].hdi_low > 0
nodes.s.focus.excludes_zero and nodes.ppc.ok
A condition cannot make an entry name from a parameter. Thus, to read a value that a workflow
parameter names, set the focus parameter of model.summary. The result field focus then
gives the mean, sd, HDI bounds, p_positive and excludes_zero of that value. R-hat is null
when the fit has only one chain, so use coalesce for it.
The rules are strict. Thus, a condition means exactly what it says:
and/or/notneed true or false. Numbers are not "truthy".- An order comparison compares numbers with numbers and text with text.
- A missing result is an error, unless it is in
existsorcoalesce.
Static validation makes sure that each referenced node runs before the branch. It also
makes sure that the node produces the field (its result_fields).
Validation¶
POST /v1/workflows:validate runs mapi_spec.workflow.validate_workflow. Each saved revision
also runs it. It reports each problem with a JSON pointer:
| Check | Example message |
|---|---|
| Unknown implementation | /nodes/0/use: node 'adf' uses 'test.nope', which is not in the node catalog |
| Parameters | /nodes/0/with/alpha: 2 is greater than or equal to the maximum of 0.5 |
| Missing or unknown ports | /nodes/4/inputs: node 'order' needs input 'data' (dataset) |
| Port types | /nodes/4/inputs/data: expects dataset but adf.result is test_result |
| References | /nodes/2/with/condition: nodes.adf.p_valu: test.adf results have no field 'p_valu' |
| Cycles | /nodes: the workflow has a cycle: a -> b -> a |
| Unreachable nodes | /nodes/6: node 'x' can never run: it needs both the then and the else arm of 'wanders' |
| Unused results (warning) | /nodes/5: the result of 'lone' is not used by any node or output |
| Models | /nodes/7/with/model: no catalog model matches 'ts.sarimx@^1' |
The platform stores a revision with errors, thus you can correct it. But the revision cannot
run (409 WORKFLOW_NOT_RUNNABLE).
Runs, caching and automation¶
A run (POST /v1/workflows/{workflow_id}/runs) is a parent run of kind workflow. The
orchestrator goes through the graph in topological order. It does these actions:
- It evaluates branches and selections itself, on JSON results only (
orchestratornodes). It records aBranchDecisionwith the values that the condition read. - It sends tests, transforms, splits, order searches, backtests, forecasts, summaries,
scores, plots, evaluations, comparisons with Diebold-Mariano or stacking, and governance
checks to sandboxed
workflow_nodechild runs. Only the sandbox decodes user data. - It starts fits as usual
fitchild runs, and sub-workflows as child workflow runs (see where nodes run).
The identity of each node is a cache key (node_cache_key). The key contains the
resolved implementation, its resolved parameters and the content of its inputs. If a node
did not change, it uses its earlier result again (status cached). For example, if you change
only the final report and run the workflow again, only the report runs again. Each node
records its status, attempt, result, outputs, child runs and cost.
Schedules run a workflow on a cron expression in a time zone (@daily, 0 6 * * 1). The
shortest interval is 15 minutes. The schedule handles daylight-saving gaps. Triggers run a
workflow each time that a dataset gets a new version. A trigger has a binding to one of the
inputs of the workflow. Terminal runs send a notification to webhooks.
Some nodes give a plot output: plot.render, model.forecast and evaluate.residuals.
The server puts each plot output through the plot sanitizer when it collects the output. An
unsafe plot fails its node with PLOT_UNSAFE. report.summary has a plots input and puts
each plot into a section of the report. The run view of the web app and the JSON report show
these plots.
Tracking and experiments in a workflow v0.11¶
A workflow run and its node runs are usual runs. Thus they have the same tracking data as other runs (Run tracking and experiments):
- A node implementation can log metrics, params, tags and log lines through its
NodeContext, with the same methods as a model (log_metric,log_params,set_tag,log_line). From v0.14, it can also log images and tables. search.gridlogs each grid point in its search run: the paramstrial.<i>.<pointer>andgrid.*, the scores with the number of the point as the step,best_<metric>and the taggrid.best_point.model.backtestlogs the metrics of each fold with the labelfold.
From v0.14, a workflow run can go into an experiment. Give
experiment_id when you start the run (POST /v1/workflows/{workflow_id}/runs,
api.workflows.run or mapi workflows run --experiment). Each node run then goes into the
same experiment. Schedules and triggers start their runs in no experiment.
Use search.grid, not a Sweep
The Sweep document is retired in v0.14. The server refuses new Sweep documents. A
workflow with a search.grid node does the same work, and its points are metrics that
you can compare on the compare page.
The inputs of a workflow must be datasets. An image collection (v0.15) cannot be the input of a workflow (Image data and collections).
The node catalog¶
GET /v1/catalog/workflow-nodes lists all implementations. For each implementation, it gives
its ports, parameter schema, result fields and an About card.
| Kind | Implementations |
|---|---|
| Statistical tests | test.adf, test.kpss, test.pp, test.zivot_andrews, test.johansen, test.engle_granger, test.ljung_box, test.jarque_bera, test.breusch_pagan, test.white, test.arch_lm, test.granger, test.acf_pacf, test.seasonal_strength, test.vif, test.cusum, test.chow |
| Data processing | transform.difference, transform.seasonal_difference, transform.log, transform.box_cox, transform.steps (all catalog preprocessing steps), transform.select |
| Train/test plans | split.holdout, split.rolling_origin, split.expanding_window, split.blocked_kfold, split.purged_kfold, split.geo_holdout, split.nested |
| Searches | search.arma_order, search.sarima_order, search.var_lag_order, search.coint_rank, search.grid |
| Models | model.fit (all catalog models), model.backtest, model.forecast, model.summary, model.score, model.lift_test |
| Evaluation | evaluate.metrics, evaluate.baseline, evaluate.residuals, evaluate.ppc |
| Comparison and selection | compare.models, compare.diebold_mariano, compare.stacking, select.best, select.threshold |
| Governance | governance.policy, governance.check (see Governance) |
| Reports | report.summary, report.evidence_pack |
| Plots | plot.render (all catalog plot recipes) |
| Control | branch.if, subworkflow.run |
Each test returns a StatTestResult. It contains these items:
- The statistic and the p-value
- The decision at the significance level:
reject, and a label such asunit_rootorstationary - The verdict in plain words
- For panels, the result for each series
The null hypothesis of KPSS is the "good" case. Thus, its labels are the opposite of the
labels of ADF. mapi_spec.stattests.decide keeps them correct.
Bayesian models in a workflow¶
Five nodes work on the draws of a Bayesian fit:
| Node | What it gives |
|---|---|
model.summary |
The mean, sd, HDI, R-hat and bulk ESS of each parameter, max_rhat, min_ess and the number of divergent transitions |
evaluate.ppc |
A posterior predictive check: the coverage of the 50% and 90% ranges, the PIT distance from uniform, Bayesian p-values and ok |
compare.stacking |
Stacking weights (or pseudo-BMA+ weights) of some fits, from the pointwise elpd_loo that model.fit keeps for data of up to 10 000 points |
model.score |
A score mode (predict, simulate, forecast or counterfactual) as a dataset that later nodes can use |
plot.render |
Any recipe of the plot catalog as a plot output. source is auto, data, model or predictions. |
model.lift_test v0.13 reads the impact group of a causal
model version (causal.impact, causal.synthetic_control or causal.did). It gives a
LiftTest for the calibration of mmm.bayesian: the total effect over the effect window,
with its sd and HDI. The calibration parameter of model.fit takes it, for example
{lift_tests: {$expr: "[nodes.lift.lift_test]"}}.
Validation finds the recipe of plot.render in the bundled recipes and in the plot catalog.
An unknown recipe gives REF_UNRESOLVED.
Tree datasets in a workflow¶
model.fit and model.backtest have the parameters nodes and join. They have the same
meaning as data.nodes and data.join of a spec
(Tree datasets). If nodes is not
set, the node reads the root node of the tree. model.score, model.forecast and
plot.render with source: predictions send a tree input as a full tree. plot.render with
source: data keeps the groups of a tree input.
Known gaps
- A backtest of a tree dataset gives the full tree to each fold. A tree-input model that reads its target from the tree can see the test window. The node shows a warning.
search.gridhas nonodesparameter.- Nodes that fit again on changed flat data, and the ablations of governance checks, do not give a tree to tree-input models.
- The HTML report does not show plot sections. Only the JSON report and the web run view show them.
Train/test plans¶
A SplitPlan describes many folds at one time (mapi_spec.splitplan). Window plans have
these fields: horizons (scored separately), a step between origins, a gap between
training and test, n_folds and an anchor. By default, the last test window ends at the
last period. The platform calculates folds from the number of periods only. Thus, the web app
shows the folds before a node runs.
expanding_window and rolling_origin match the scikit-learn TimeSeriesSplit. An
expanding fold is exactly the existing as_of split with its gap. geo_holdout holds out
full markets, optionally only over their last periods. nested runs an inner plan in each
outer training window, for correct tuning.
The gallery¶
| Template | What it automates |
|---|---|
ts_readiness_arimax |
Unit-root tests, differencing that branches decide, order search, an expanding-window backtest against naive benchmarks, selection, governance and a report |
var_vecm_cointegration |
A Johansen test. If the series are tied together, a VECM with the selected rank. If not, differences, a lag search and a VAR. Granger tests and a report. |
mmm_governed |
Driver collinearity, the mmm-framework marketing mix model with a holdout, the governance policy, a report and an evidence pack |
geo_lift_mmm |
A Bayesian synthetic control of a geo-lift test, the lift test that it gives (model.lift_test), mmm.bayesian calibrated with that lift test, the plots of the test and of the calibration, and a report. See the cookbook. |
forecast_bakeoff |
A backtest of SARIMAX, ETS, Theta and the smooth-trend model on the same folds, against benchmarks. The platform fits the best model again. |
sci_spatial_ladder |
sci.areal_bym2 with iid, ICAR and BYM2 area effects, compared by elpd_loo and stacking weights. The best model, the range of its covariate effect, fits with wider and narrower priors when the effect is clear, a convergence check, a risk map and a report. See the cookbook. |
sci_network_ladder |
sci.network_latent_space with covariates, sociality and a latent space, compared by elpd_loo and stacking weights. The pair effects of the best model, a network plot, simulated networks with their statistics and a report. See the cookbook. |
sci_phylo_signal |
Four fits of sci.phylo_regression (no tree, Brownian motion, Pagel's lambda, Ornstein-Uhlenbeck), ranked by elpd_loo and weighed by stacking. A branch on the slope of one predictor of the winner: does its range exclude zero? A plot of the tree and a report. See the cookbook. |
sci_calibrate_then_design |
A fit of sci.heat_plate with one calibration run held out. A branch on the error on that run and on R-hat. If the calibration is good enough, the predicted temperature field of a new heater layout, as a map. A report. See the cookbook. |
bayes_workflow_audit |
Any Bayesian model: a fit with prior draws, a convergence gate, the posterior predictive check, prior sensitivity, the recovery of known values, the plots and a report. See the cookbook. |
sci_image_model_check |
An image model: a fit, residual maps, the observed image next to predicted images, the posterior predictive check and a report. See the cookbook. |
To make a workflow from a template, use POST /v1/workflows with {"template": "<id>"}.
Then edit its parameters or nodes, the same as for all other workflows. The
complex workflows tutorial shows the patterns of the
science recipes in the workflow editor.
Known gaps of the science recipes
governance.checkwithsensitivity.priorcannot find the prior widths ofsci.areal_bym2, so the check is skipped.sci_spatial_ladderfits the model again with these widths in its place.- The graph check of
sci_network_ladderplots the statistics of the simulated networks only. The plot does not mark the statistics of the observed network. core.prior_predictivecannot draw the prior draws ofsci.heat_plate. Some science models (for examplesci.cmb_fieldandsci.phylo_regression) keep no prior draws.bayes_workflow_auditthen shows a failed prior plot and continues.- Only
sci_spatial_ladderran end to end on a live dev stack and in a browser. The six science and audit recipes run through the server orchestrator in a slow test, with small fits.