Skip to content

finprint

Analysts import finprint as fp

Finprint writes down your pandas prep as you do it. Save the cleaned file and a recipe lands beside it: inputs by hash, every step with its row counts, and the source of your own helpers.

It has no dependency on the rest of UMP, so it installs in any notebook environment. Runtime needs are pandas, numpy, pydantic and httpx. The recipe format is part of the UMP standard and keeps its wire names (ump-recipe), so recipes saved under the package’s old name still verify.

  1. Install: pip install -e 'packages/finprint[notebook]' from a checkout.
  2. Start a session, read through it, work as usual, and save.
  3. Verify the recipe reproduces the file.
import finprint as fp
rec = fp.session("weekly prep")
raw = rec.read_csv("raw.csv", parse_dates=["week"])
clean = raw.drop(index=[0]) # recorded: "Dropped 1 row: 6 to 5"
clean["tv"] = clean["tv"].interpolate() # recorded with the expression, so it replays
@rec.step # your own function: recorded with its source
def rebase_to_net(df, fee=0.15):
out = df.copy()
out["spend_net"] = out["spend"] * (1 - fee)
return out
clean = rebase_to_net(clean, fee=0.15)
rec.save(clean, "clean.csv") # writes clean.csv and clean.recipe.json
fp.verify("clean.recipe.json").status # "reproduced"

In a notebook, %load_ext finprint starts a session and also records the cells you ran.

CallWhat it does
fp.session(name)Starts recording. Read through rec.read_csv, read_excel, read_parquet or read_catalogue.
@rec.stepRecords one of your functions with its source, so the step can replay.
rec.save(frame, path)Writes the data and <name>.recipe.json beside it.
fp.verify(recipe)Replays the recipe and asks: does it still make this exact file?
fp.apply(recipe, inputs)Runs the same prep on new data, such as next quarter’s longer file.
fp.describe(recipe)The steps in plain words.

The two answer different questions.

  • verify refuses any input whose hash differs. Use it to prove a file came from its recipe.
  • apply compares no hashes. It binds inputs by id, then name, then file basename, and checks that each step adds and removes the same columns it did when recorded.
res = fp.apply("clean.recipe.json", {"raw.csv": "q3_raw.csv"})
res.status, res.frame, res.notes # "applied" or "refused" (res.code and res.step_id say why)
fp.row_bound_steps(fp.load_recipe("clean.recipe.json")) # steps tied to specific rows, such as drop(index=[0])
Trust and named-step code

A recipe can carry the source of your @rec.step functions. Code that this machine did not record is printed and runs only after you trust it: --trust on the command line, or a yes at the prompt. The trust store lives under FINPRINT_HOME.

Things that are not a DataFrame

Tracked frames are proxies. Pandas’ module-level functions reject them with a TypeError that names the alternative: use a.merge(b) instead of pd.merge(a, b), rec.concat([...]) instead of pd.concat, and rec.call(pd.to_datetime, a["week"]) for other functions you want recorded.

Terminal window
finprint show clean.recipe.json # plain step list
finprint verify clean.recipe.json --inputs data/ # exit 0 only when reproduced
finprint apply clean.recipe.json --input raw.csv=q3_raw.csv -o q3_clean.csv