Skip to content

ump_spec.rules.recipes

module ump-spec

Transform recipes on the catalogue side (POC blueprint TRANSFORM-RECIPES §6 and §7). Pure functions.

  • describe_steps: one plain line per recorded step (“Dropped 2 rows: 156 to 154”, “Added spend_net”).
  • recipe_badges and recipe_plain: the catalogue’s words for a RecipeRef (“Made from Northwind raw delivery and promo_calendar.csv (not in the catalogue) in 5 steps. Reproduced on the analyst’s machine on Oct 27, 2026 (self-reported).”).
  • to_openlineage and to_prov: the recipe in existing wire formats (load-bearing decision 9).
  • recipe_for_agent: the recipe without notebook cells or step code, for agent principals. Cell source and step code are recorded as the analyst wrote them, so they can hold anything; steps, counts and hashes cannot.

The client package (finprint) keeps its own copy of the describe and export logic because analysts install it without ump_spec. packages/finprint/tests/test_spec_alignment.py checks that both give the same step words, OpenLineage event and PROV document for the same recipe. Nothing here runs recipe code: a recipe is data, and the catalogue never executes it.

function

def as_recipe(recipe: TransformRecipe | Mapping[str, Any]) -> TransformRecipe

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 45

function

def column_origins(
recipe: TransformRecipe,
) -> tuple[dict[str, set[Origin]], dict[str, str]]

For each output column, the input columns it came from, and a note for columns of unknown origin.

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 430

function

def describe_step(step: RecipeStep, names: dict[str, str], *, tags: bool = True) -> str

One step in words. tags adds the terminal’s [cell N] and [not replayable] markers; a page that shows the cell and replayability on their own leaves them off.

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 257

function

def describe_steps(recipe: TransformRecipe | Mapping[str, Any]) -> list[str]

The step list in words, one line per step (the same words as finprint show).

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 281

function

def divergent_step_number(ref: Mapping[str, Any]) -> int | str | None

Step number (1-based) where a replay first differed, "output" when only the file differed.

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 309

function

def recipe_badges(ref: Mapping[str, Any] | None) -> list[str]

Badges for a dataset’s recipe. When the recipe’s output is not the uploaded file, “Describes a different file” leads and a “Reproduced” replay is dropped (it reproduced another file). Then one of Reproduced, Not checked, Didn’t reproduce at step N, Can’t be replayed, Inputs missing; plus Partly recorded.

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 330

function

def recipe_for_agent(recipe: Mapping[str, Any]) -> dict[str, Any]

The recipe without notebook cells, named-step code or the globals that code reads (agents read steps, counts and hashes). Still a valid TransformRecipe, so the exporters take it.

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 397

function

def recipe_plain(
ref: Mapping[str, Any] | None,
titles: Mapping[str, str] | None = None,
) -> str | None

A few sentences for the catalogue: “Made from <inputs> in N steps. Reproduced on the analyst’s machine on Oct 27, 2026 (self-reported).” Matched inputs are named by their catalogue title (titles: dataset id to title), else the recipe’s own input name, else “a catalogued dataset” (never a raw id); a drifted match adds “(changed since it was catalogued)”; other inputs keep the recipe’s name with “(not in the catalogue)”. When the recipe’s output is not the uploaded file, that sentence comes before the replay result. Every replay result is the analyst’s own report, so it says self-reported.

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 357

function

def step_names(recipe: TransformRecipe) -> dict[str, str]

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 99

function

def step_rows(
recipe: TransformRecipe | Mapping[str, Any],
*,
with_code: bool = True,
) -> list[dict[str, Any]]

The steps for a page: number, words, row counts, column changes and (unless with_code is off) the named step’s code. Arguments stay out: they are in the recipe for replay, not for reading.

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 288

function

def to_openlineage(
recipe: TransformRecipe | Mapping[str, Any],
*,
producer: str = DEFAULT_PRODUCER,
matched: Mapping[str, str] | None = None,
output_dataset_id: str | None = None,
) -> dict[str, Any]

An OpenLineage 2-0-2 RunEvent (eventType COMPLETE) for the recipe, with schema and columnLineage facets.

matched (input id to catalogue dataset id) names matched inputs in the ump-catalogue namespace, and output_dataset_id names the output the same way; without them the event is the client package’s.

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 493

function

def to_prov(
recipe: TransformRecipe | Mapping[str, Any],
*,
matched: Mapping[str, str] | None = None,
output_dataset_id: str | None = None,
) -> dict[str, Any]

W3C PROV-JSON: inputs, intermediate frames and the output as entities, steps as activities. matched and output_dataset_id add ump:dataset_id to the catalogued entities.

Source: packages/ump-spec/src/ump_spec/rules/recipes.py, line 539

NameTypeValueDescription
DEFAULT_PRODUCER'urn:ump:ump-data'
RECIPE_SCHEMA_ID'https://schemas.ump.dev/v1/transform-recipe.json'
STATUS_WORDS{'not_checked': 'Not checked', 'reproduced': 'Reproduced', 'not_reproduced': ...