Create clean, reproducible Jupyter notebooks that are easy to skim, rerun, and handoff. Treat the notebook as a reader-facing analysis artifact, not a scratchpad dump. Notebook work is not complete until the notebook executes successfully top-to-bottom, or the execution gap is called out with the exact validation steps needed to reproduce it.
-
Lock the notebook mode and scope.
Decide whether the notebook is an analysis report, experiment log, diagnostic notebook, data-quality check, market-sizing calculation, model exploration,
tutorial, or companion artifact for a report. Identify the reader, decision,
expected handoff, required inputs, and whether the task calls for a new notebook or targeted edits to an existing one.
-
Inspect or scaffold with notebook-safe tooling.
Prefer JupyterLab, nbformat, nbclient, or an existing scaffold utility over hand-editing raw JSON. When editing an existing notebook, preserve its intent and minimize JSON churn. Avoid reordering cells unless it clearly improves the top-to-bottom story. If raw JSON editing is unavoidable,
validate the notebook structure before finishing.
-
Structure the notebook for the chosen mode.
For analytical notebooks, default to:
## tl;dr
## Context & Methods
## Data
## Results
## Takeaways
Write tl;dr and takeaways after reviewing executed outputs. Use concrete observed values, visible patterns, rows, or charts, not assumptions. Include a ### Key Assumptions subsection in Context & Methods when assumptions affect correctness.
For tutorials or walkthroughs, adapt the same discipline to a teaching flow:
## Goal
## Setup
## Steps
## Checks
## Next Steps
-
Build a clear data and computation path.
Separate setup, imports, parameters, data loading, data preparation,
calculations, visualizations, and interpretation. If the notebook uses both SQL and Python, keep complex SQL in SQL cells or separate query files rather than large embedded Python strings unless there is a clear reason. Use descriptive variable names and keep each code cell focused on one step.
-
Use data sources deliberately.
When a notebook needs table data, first use ~~structured_data to confirm table choice, schema, partition filters, sample rows, and query-submission policy. Use the relevant source connector when available, then fall back to exports or pasted SQL when needed. Use ~~operations_logs for freshness or lineage checks when they matter. Record query permalinks, request IDs, source paths, dashboard links,
extract names, or other source artifacts in the notebook context for any executed result that supports the analysis. Keep heavy queries filtered and bounded instead of turning the notebook into a broad live-source scan.
-
Make cells readable and bounded.
Add concise markdown headers before most code cells. Keep headers brief and action-oriented, such as ### 1. Load Data, ### 2. Validate Inputs, or ### 3. Plot Results. Favor several short cells over one large mixed-purpose cell. Keep prose short: explain purpose, assumptions, and expected result, not every line of code. Split multiple tables or charts across separate cells instead of dumping all outputs from one cell.
-
Validate results before writing conclusions.
Check that key numbers, charts, and takeaways match executed outputs. Bound raw debug output, oversized tables, and noisy logs. If a result is surprising, add a local reasonableness check, small sample inspection, or reconciliation against a trusted source before promoting it to the summary.
-
Execute and record validation status.
Run the notebook top-to-bottom when the environment allows:
python -m jupyter nbconvert --execute --to notebook --inplace path/to/notebook.ipynb
Optional local setup when needed:
uv pip install jupyterlab nbformat nbclient ipykernel
If execution is not possible, say so explicitly and provide the exact command,
missing dependency, credential, data access, kernel, or environment step needed to validate locally.