Skip to content

ump-workflow

Sharing models import ump_workflow as uw

A workflow bundle is a folder of data and source, never pickles. Someone else can read the code, trust it, and run your saved fit on new weeks.

Here is the case it was built for. An analyst fits a weekly sales model in a notebook with her own plain functions and saves it as a bundle. Next quarter a colleague on another laptop opens the folder, reads the code, trusts it, and scores eight new weeks. When the new file skips two weeks, or TV spend runs past anything the fit saw, the run says so and refits with the saved seed. If he never trusts the code, nothing is imported, and he still gets the fit-time numbers from the bundle’s summary, labelled as such.

Most analysts reach it through babelfin’s fin.Workflow. This package is the runtime underneath. It depends on ump-spec, finprint, numpy and pandas, and never on a modelling library.

  1. Install: pip install -e 'packages/ump-workflow' (add the serve extra to serve bundles).
  2. Write a module with plain functions: fit, to_state, from_state and one function per question.
  3. Save, open, run.
sales_model.py
import numpy as np
def fit(df, *, seed, params):
... # returns any model object, e.g. bootstrap draws of coefficients
def to_state(model):
return {"n": model["n"]}, {"beta": model["beta"]} # JSON-safe meta, plain arrays
def from_state(meta, arrays):
return {"beta": np.asarray(arrays["beta"]), "n": int(meta["n"])}
def tv_roi(model, df):
return model["beta"][:, 1] # one value per draw
import ump_workflow as uw
c = uw.contract_from_frame(train, time="week", target="sales", freq="W-SUN")
path = uw.save_workflow("out/weekly_sales", code_path="sales_model.py", data=train, contract=c,
questions=questions, name="Weekly sales", seed=11, params={"draws": 200},
author="user:sam.patel")
b = uw.open_bundle(path) # hashes checked, data parsed, nothing run
b.code # read it first
b.trust() # then trust its hash
res = b.run(new_df) # scores by default
res.route, res.source, res.answers["tv_roi"], res.notes

Each question is a dict with name, ask, function and a seven-field estimand template, so every answer comes back with its estimand and an interval taken from the quantiles of the draws.

CallWhat it does
uw.contract_from_frame(df, ...)The input contract: the shape new data must have. Category levels are stored as hashes, never values.
uw.save_workflow(path, ...)Fits, checks that the state round-trips to the same draws, computes fit-time answers and a summary, then writes. Nothing is written on a refusal.
uw.open_bundle(path)Verifies every file hash before reading anything. Runs nothing.
OpenBundle.run(df)Scores new data, or refits when the data calls for it, and labels which happened.
OpenBundle.check(df)The contract check alone.
ump_workflow.serve.build_app(path)Puts one bundle behind UMP’s engine adapter contract as a FastAPI app.
SituationRouteNote on the result
New weeks follow the training data, spend in rangescore with the saved statenone
A gap, an overlap or irregular timerefit on the given data with the saved seedHISTORY_ROUTED_TO_REFIT
Spend outside the training rangerefitINPUT_OUT_OF_SUPPORT
Code not trustedanswers from the bundle’s summaryroute not_available, labelled

Pass mode="refit" to refit on purpose, or allow_refit=False to stop at the labelled fallback instead.

Rules the bundle keeps
  • Never unpickle. Arrays are saved with numpy.save and loaded with allow_pickle=False.
  • Load order. Every file is checked against workflow.json and SHA256SUMS first. A mismatch, a missing or unlisted file, or a link refuses the bundle before any content is read.
  • Round trip on save. from_state(to_state(model)) must give the same draws on the training data, or the save is refused with WORKFLOW_STATE_LOSSY.
  • Answers are draws. A question must return a one-dimensional array with at least two values. With no interval level declared, the level is 0.9, listed as proposed.
  • No personal data. The author is a principal id such as user:sam.patel, never an email. Training data is stored only with include_data=True.
  • Shared trust is an approval. Through core, a bundle’s hashes are trusted only from a validated manifest approved by someone other than its author.
Terminal window
ump-workflow inspect out/weekly_sales --json
ump-workflow check out/weekly_sales new_weeks.csv
ump-workflow run out/weekly_sales new_weeks.csv --trust -o answers.json
ump-workflow serve out/weekly_sales --port 8210

Exit codes: 0 ok, 1 fallback (summary answers), 2 usage, 3 bundle refused or unreadable. The serve port default, 8210, is proposed.

Served routes
MethodPathReturns
GET/v1/metadataAdapter metadata: the bundle’s family, the answer and fit operations, one estimand per question.
GET/v1/questionsEach question’s name, wording and estimand.
GET/v1/contractThe input contract.
POST/v1/answer{rows, mode?, allow_refit?, questions?} gives the same result as run(df).to_dict().
POST/v1/fitThe same with mode="refit".

The server runs the code only when an approval, or the machine’s own trust store, vouches for its hash. Otherwise it answers from the labelled summary.