Notebook Interface

The wrapper facade API gives generated models analyst-facing names, tables, reports, and scenarios. The modelwright.notebooks module adds the next layer: pandas-backed helpers that display those facade views naturally in a live Jupyter kernel.

Install the optional notebook dependency when using these helpers:

python -m pip install 'modelwright[notebook]'

The core package and modelwright.wrappers do not require pandas.

The tracked examples also include real notebook files under examples/notebooks/. They are the best starting point for users who want a literate, open-and-run walkthrough rather than copying code from this guide.

When using the tracked source-tree examples from a notebook stored under tmp/notebooks/, add the repository root to sys.path before importing from examples:

from pathlib import Path
import sys

repo_root = Path.cwd().resolve()
while repo_root != repo_root.parent and not (repo_root / "pyproject.toml").exists():
    repo_root = repo_root.parent

if not (repo_root / "pyproject.toml").exists():
    raise RuntimeError("Could not find the Modelwright repository root.")

sys.path.insert(0, str(repo_root))

Boundary

Notebook helpers do not edit generated source code and do not recreate a spreadsheet UI. They convert declared wrapper views into DataFrames so an analyst can inspect model structure, change scenario inputs, recalculate, and compare results without manually reading raw Sheet!A1 dictionaries.

Minimal Example

Assume a generated model module exposes calculate(inputs=None):

def calculate(inputs=None):
    inputs = inputs or {}
    base = inputs.get("Inputs!B2", 100)
    growth = inputs.get("Inputs!B3", 0.1)
    return {
        "Summary!B2": base * (1 + growth),
        "Summary!C2": base * 2,
        "Summary!B3": "ok",
        "Summary!C3": base + 5,
    }

Declare a facade around the generated model:

from modelwright.wrappers import ModelFacade, cell, report, table

facade = ModelFacade(
    generated_model,
    cells=[
        cell("Inputs!B2", name="base", label="Base volume", role="input", unit="t"),
        cell("Inputs!B3", name="growth", label="Growth rate", role="input", unit="fraction"),
        cell("Summary!B2", name="projected", label="Projected volume", role="output", unit="t"),
        cell("Summary!B3", name="status", label="Status", role="output"),
    ],
    tables=[
        table(
            "summary_grid",
            sheet="Summary",
            range_ref="B2:C3",
            row_labels=["volume", "status"],
            column_labels=["primary", "secondary"],
        )
    ],
    reports=[
        report("summary", cells=["base", "projected", "status"], tables=["summary_grid"]),
    ],
)

Render declared inputs and outputs:

from modelwright.notebooks import inputs_frame, outputs_frame

scenario = facade.scenario(name="shock", inputs={"Inputs!B2": 50}).with_input("Inputs!B3", 0.2)

inputs_frame(facade, scenario)
outputs_frame(facade, scenario)

In Jupyter, those calls display tidy DataFrames with names, labels, cell references, roles, units, values, and value-presence flags.

Render Tables

Declared rectangular tables become DataFrames whose visible row and column labels come from the wrapper declaration:

from modelwright.notebooks import table_frame

summary = table_frame(facade, "summary_grid", scenario)
summary

Workbook provenance stays attached to the DataFrame:

summary.attrs["sheet"]
summary.attrs["range_ref"]
summary.attrs["cell_refs"]

Render Reports

Reports return a small mapping with a cell DataFrame and named table DataFrames:

from modelwright.notebooks import report_frames

frames = report_frames(facade, "summary", scenario)
frames["cells"]
frames["tables"]["summary_grid"]

Compare Scenarios

Scenario comparison is the core notebook loop:

from modelwright.notebooks import compare_scenarios_frame

baseline = facade.scenario(name="baseline", inputs={"Inputs!B2": 100, "Inputs!B3": 0.1})
shock = baseline.with_input("Inputs!B2", 120)

compare_scenarios_frame(facade, baseline, shock)

The comparison DataFrame includes declared name, label, cell reference, baseline value, scenario value, absolute change, percent change where numeric and meaningful, unit, role, and description. Text comparisons and zero-baseline percent changes remain explicit rather than raising.

Scenario Inputs

Use scenario_frame to inspect the exact overrides being sent to the generated model:

from modelwright.notebooks import scenario_frame

scenario_frame(shock)

Alpha Limits

The notebook helpers are provisional. They are intended to make wrapped generated models usable in a live Python kernel, not to promise a complete end-user application.

Current non-goals:

  • full spreadsheet UI recreation;

  • dashboard server or widget framework;

  • automatic recovery of workbook semantic names or table meanings;

  • visual formatting or workbook editing;

  • Excel-backed recalculation equivalence;

  • stable public API compatibility before real notebook workflows harden the design.