Onboarding worklist
Use Case 2.4 from the design overview (§2.4). A repetitive but not-fully-automatable process — new-hire onboarding — tracked as a sheet where humans tick checklist items and Folio derives the progress summary.
The shipped sheet is
examples/onboarding/.
The shape
contract.yaml columns: id string PK hire_name string human-editable role string human-editable start_date date human-editable owner string human-editable (HR partner email) status string human-editable ("pending" / "in_progress" / "complete") checklist array human-editable — JSON [{label, done}, ...] progress string derived (python — "done/total" from checklist)The checklist is structured JSON stored in one column. That’s
unusual but useful: each onboarding has its own list of items, and the
contract doesn’t need to enumerate every possible step.
Why structured JSON in a single column?
Two alternatives are clunkier:
- One row per (onboarding, step). Forces every report to filter by
idfirst. Multiplies row count by ~5x. Loses the “this onboarding” identity. - A separate
onboarding-steps/sheet referenced byonboarding.id. Right answer for a deeper workflow, but overkill for a 5-step checklist.
A logicalType: array column hits the sweet spot: schema-typed (Folio
checks it’s a JSON array on write), but not over-normalized.
The derivation
targets: [progress]inputs: [checklist]kind: pythonscript: progressimport json, sys
inputs = json.loads(sys.argv[2])items = inputs.get("checklist") or []total = len(items)done = sum(1 for item in items if isinstance(item, dict) and item.get("done"))print(f"{done}/{total}")Cache hit on every run unless the checklist changes.
Walking through the sheet
folio validate examples/onboardingfolio materialize examples/onboarding --actor agent:demo
folio query examples/onboarding \ "SELECT status, COUNT(*) AS n FROM records GROUP BY status"[{"status":"in_progress","n":2}, {"status":"pending","n":1}, {"status":"complete","n":1}]Open the Viewer:
folio serve examples/onboarding --port 3000 --actor agent:humanThe Records tab lets HR partners:
- update
status,owner,start_dateinline, - toggle items in the
checklist(JSON edit — paste the modified array in the inline editor, then tab out), - see the
progresscolumn update on the next materialize.
A typical lifecycle
-
HR creates a row. When a new hire signs, an HR partner upserts a row with the role-specific checklist template.
Terminal window folio upsert examples/onboarding --actor agent:hr --file - <<EOF{"id":"ob_005","hire_name":"...","role":"...","start_date":"...","owner":"alice@hr","status":"pending","checklist":[{"label":"Laptop issued","done":false},{"label":"Slack invited","done":false},...]}EOF -
Materialize.
progressis set to"0/5". -
HR works the list. Tick items as they happen. Each tick is one upsert. Materialize after each batch (or set up a cron).
-
progressupdates on every materialize. The Viewer’s status column shows it on the records grid.
Extending
- Status auto-derived. A second
pythonderivation that readschecklistand setsstatustocompletewhen all items aredone,pendingwhen none are,in_progressotherwise. - Reminders. A daily script that lists onboardings where
start_date < today + 7dandprogressis below"3/5". - Per-role templates. A separate
onboarding-templates/sheet keyed byrole, with across_sheetderivation that copies the template into a new row’schecklist.
See also
- examples/onboarding/README.md
pythonderivation — the kind used here.folio query— the SQL surface for dashboards.