Skip to content

DS 301 · Module 1

From Notebook to Repeatable Analytics Workflow

Start by defining a data product’s users, inputs/outputs, and owner, refactor exploratory code into explicit functions and stages, separate configuration and secrets from code, use data contracts to fail a pipeline early, then refactor a notebook into a pipeline in a Guided Lab.

Start Section 1
0 / 5 completed
Sync my progressJoin a session to keep progress across devices and share it with your instructor

Info

After you join, your completed lesson numbers are sent to your instructor together with your learner code, so they can see your progress. Use the codes your instructor gave you, and do not enter your name, company secrets or other personal data. Progress is kept with the rest of that session's data.

Learning objectives

  • Define users, inputs, outputs, SLA/freshness, quality expectations, ownership
  • Refactor exploratory code into idempotent stages with explicit inputs/outputs
  • Separate config from code; never hardcode secrets; define dev/test/prod concepts
  • Fail early on schema/quality violations; distinguish warning from blocking errors
  • Build ingest→validate→transform→analyze→publish stages with checkpoints

What Is a Data Product / Analytics Workflow?

40 min

What Is a Data Product / Analytics Workflow?

Learning objective: define users, inputs, outputs, SLA/freshness, quality expectations, ownership.

Every month you manually run a notebook that produces a report your manager reads. Last month you were out, and a colleague had to take over with no idea which source file to use, where the output belongs, how often it should refresh, or who to call when something breaks. This analysis was never actually turned into a product, it only lived in your head and on your laptop. The first step toward a data product is not writing code; it is writing down the tacit knowledge: who uses this output, how fresh they expect it to be, what quality counts as trustworthy, and who is accountable for its correctness.

Five starting questions for any data product
QuestionThis course’s dataset answer
Who are the users?Managers and business-unit contacts at the monthly operating review
What is the input?The monthly analytics package: run_id, source_system, batch_id, event_time, ingested_at, entity_id, metric, value, schema_version
What is the output?A monthly metric table aggregated by source_system and metric, with a verification record
Freshness expectationLast month’s data completed by the third business day of the month
OwnerThe person who maintains it, fixes bugs, and answers "where did this number come from?"
This course’s dataset (schema first, product talk second)
import pandas as pd

# clean: monthly analytics package, one row is a metric observation for a source system, batch, and entity
df = pd.read_csv("DS301_Analytics_Package_Clean.csv", parse_dates=["event_time", "ingested_at"])
df.shape
df.dtypes
df[["run_id", "source_system", "batch_id", "metric", "value", "schema_version"]].head()

# Ask first: who owns this data? If schema_version changes, who needs to be told?
df["schema_version"].unique()

One-off notebook

"I run a notebook and paste the result into a report." Nobody knows who runs it next, how stale the input has to be before it’s a problem, or what "good enough" output looks like.

Data product

Users and purpose
Write down who uses this output and what decision it supports.
Input/output contract
Explicitly define the input schema and the output format, location, and refresh cadence.
Quality and owner
Define what "trustworthy output" means, and who is accountable for correctness and for catching failures.

Info

Human, AI, Human: a person defines the data product’s users, purpose, and quality expectations; AI may help draft pipeline code; a person verifies the output matches the contract and remains responsible for it.
  • Only you know where to pull the raw data from, and the whole process lives in your head
  • Nobody knows how often the output should refresh, and nobody checks whether it actually does
  • "Is this number right?" can only be answered by the original author's word, with no verification record
  • Running it on a different machine or by a different person fails outright because of paths or credentials
Check yourself

A monthly analytics notebook lives only on its author’s laptop, with nobody else aware of its input sources, refresh cadence, or quality bar. What is this analysis most fundamentally missing?

A monthly analytics notebook lives only on its author’s laptop, with nobody else aware of its input sources, refresh cadence, or quality bar. What is this analysis most fundamentally missing?

Key takeaway: Completing the steps is not enough; the result must be verifiable, reproducible, and explainable.

Notebook → Functions → Stages

45 min

Notebook → Functions → Stages

Learning objective: refactor exploratory code into idempotent stages with explicit inputs/outputs.

An exploratory notebook is often one long chain of cells executed top to bottom: loading, cleaning, filtering, aggregating, and plotting are all tangled together, with variables depending on whatever earlier cells left in memory. Change cell 3 and you have to rerun everything after it. That style is fine for exploration, but it does not survive becoming a reliable workflow: nobody can rerun just the "cleaning" step without touching everything else, and no single step can be unit-tested on its own. Turning exploratory code into functions and stages is the difference between "one long spell" and "a set of blocks you can inspect and rerun individually."

From a long chain of cells to a stage with explicit inputs/outputs
# Exploration: a long chain of interdependent cells (hard to rerun, hard to test)
df = pd.read_csv("DS301_Analytics_Package_Clean.csv")
df = df[df["schema_version"] == "v2"]
df["value"] = df["value"].fillna(0)
summary = df.groupby(["source_system", "metric"])["value"].sum()

# Refactored into stages: each function has a clear input/output and can be tested or rerun on its own
def load_raw(path: str) -> pd.DataFrame:
    return pd.read_csv(path, parse_dates=["event_time", "ingested_at"])

def filter_current_schema(df: pd.DataFrame, version: str) -> pd.DataFrame:
    return df[df["schema_version"] == version].copy()

def fill_missing_values(df: pd.DataFrame) -> pd.DataFrame:
    df["value"] = df["value"].fillna(0)
    return df

def summarize_by_metric(df: pd.DataFrame) -> pd.DataFrame:
    return df.groupby(["source_system", "metric"], as_index=False)["value"].sum()
Engineering value of splitting into functions
ValueExplanation
Explicit interfaceEach function’s input/output types are clear without reading the whole file
Independently testablefill_missing_values can be unit-tested without running the whole pipeline first
Independently rerunnablesummarize_by_metric can be rerun without re-loading and re-cleaning the whole dataset
ReusableThe same filter_current_schema can be imported by other pipelines instead of copy-pasted
  1. Find the natural breakpoints
    Locate where loading, cleaning, filtering, and aggregating naturally divide in the exploratory notebook.
  2. Write one function per stage
    Give each stage an explicit input type and output type, and avoid relying on global variables.
  3. Remove hidden dependencies
    Confirm no function depends on a variable left over in memory from an earlier cell.
  4. Add tests for critical functions
    Write at least one test for an error-prone stage, such as cleaning or aggregation.

Warning

Splitting code into functions is not about making it "look more professional", it is about letting each stage be independently verified and rerun. If the result can still only be rerun as one giant block, the dependencies were never really separated.
Narrowing down a bug once stages are separated

Suppose the aggregated total comes out negative. Because each stage has a clear output, you can check them in order: does load_raw() return the expected row count? Does filter_current_schema() leave a reasonable number of rows? Did fill_missing_values() fill in zeros where it should not have? Printing each function’s intermediate output lets you pinpoint which stage is wrong in minutes, instead of reading through one long tangled block of code from the top.

Check yourself

What is the main engineering value gained from refactoring an exploratory notebook into multiple functions?

What is the main engineering value gained from refactoring an exploratory notebook into multiple functions?

Key takeaway: Completing the steps is not enough; the result must be verifiable, reproducible, and explainable.

Configuration, Secrets, and Environments

40 min

Configuration, Secrets, and Environments

Learning objective: separate config from code; never hardcode secrets; define dev/test/prod concepts.

A colleague hardcodes a database password into the first cell of a notebook, for convenience while developing. Three months later that notebook is shared with another team for a training demo, and the password leaks along with it. An even more common problem: the same code runs fine on your laptop but fails outright on another machine or against a different database, because paths, credentials, and table names are all baked into the code. Pulling configuration and secrets out of the code is the precondition for the same code running safely across dev, test, and prod.

From hardcoding to separated config/secrets
# Wrong: credentials and environment settings hardcoded into the code
DB_PASSWORD = "sup3r-secret-2026"  # never do this
INPUT_PATH = "/Users/alice/Desktop/data.csv"

# Right: read from environment variables / a secret manager; the code itself carries no secrets
import os

DB_PASSWORD = os.environ["DS301_DB_PASSWORD"]  # injected by the secret manager, never committed to version control
ENVIRONMENT = os.environ.get("DS301_ENV", "dev")  # dev / test / prod
INPUT_PATH = os.environ["DS301_INPUT_PATH"]  # points at a different data source per environment
How dev, test, and prod differ
EnvironmentTypical useData source
devDevelopment and debugging; free to rerun and modifySmall samples or synthetic data
testVerifying pipeline logic with settings close to productionA fixed test dataset with predictable results
prodProducing the results actually delivered to usersReal data sources; changes require review
  1. Find every hardcoded value
    Check the code for passwords, paths, and table names written directly into it.
  2. Move them into environment variables or config
    Read these values from the environment or a config file instead, so the code itself carries no secrets.
  3. Separate dev/test/prod settings
    Make sure the same code runs correctly across environments through different environment configurations.
  4. Confirm secrets are never committed
    Check version control settings to ensure secret files can never be accidentally committed.

Warning

Any password, API key, or token that is ever hardcoded into code, a notebook, or committed to version control should be treated as already leaked, it must be revoked and rotated, not just deleted from that one line afterward.

Environment variables only

  • Simple to set up, fine for a small team or solo project
  • You must manually ensure every machine and CI environment sets the variables correctly
  • No built-in access logging or automatic rotation

With a secret manager

  • Secrets are centralized, with configurable access permissions and expiry
  • Every read of a secret can be logged: who accessed it and when
  • Supports automatic rotation, reducing the risk of credentials that never change
Check yourself

A pipeline’s code has a database password hardcoded directly into it, for developer convenience. What is the main risk of doing this?

A pipeline’s code has a database password hardcoded directly into it, for developer convenience. What is the main risk of doing this?

Key takeaway: Completing the steps is not enough; the result must be verifiable, reproducible, and explainable.

Data Contracts and Pipeline Validation

45 min

Data Contracts and Pipeline Validation

Learning objective: fail early on schema/quality violations; distinguish warning from blocking errors.

An upstream system silently misspells one of the values in the metric column, from "conversion_rate" to "converion_rate," without telling anyone. Your pipeline never checks this and feeds the data straight into the aggregation logic; that metric quietly gets bucketed into "other," the monthly report undercounts an entire product line’s conversion rate, and nothing ever raises an error. A data contract writes down what the data is supposed to look like, and fails loudly and early when it does not, instead of letting bad data silently flow downstream.

Validating against a contract before data enters the pipeline
ALLOWED_METRICS = {"conversion_rate", "active_users", "avg_response_ms"}
REQUIRED_COLUMNS = {"run_id", "source_system", "batch_id", "event_time", "ingested_at", "entity_id", "metric", "value", "schema_version"}

def validate_contract(df: pd.DataFrame) -> list[str]:
    errors = []
    missing = REQUIRED_COLUMNS - set(df.columns)
    if missing:
        errors.append(f"missing required columns (blocking): {sorted(missing)}")

    unknown_metrics = set(df["metric"].unique()) - ALLOWED_METRICS
    if unknown_metrics:
        errors.append(f"unknown metric values found (blocking): {sorted(unknown_metrics)}")

    null_ratio = df["value"].isna().mean()
    if null_ratio > 0.2:
        errors.append(f"value null ratio too high (warning, needs human review): {null_ratio:.1%}")

    return errors

errors = validate_contract(df)
blocking = [e for e in errors if "blocking" in e]
if blocking:
    raise ValueError(f"Pipeline failed early on contract violation: {blocking}")
Distinguishing warnings from blocking errors
TypeExampleHandling
Blocking errorMissing required column, metric value outside the allowed setStop the pipeline; do not let incomplete or untrustworthy data flow downstream
Warningvalue null ratio is elevated but still within an acceptable rangeLog a warning and continue, but flag it for the owner to confirm
  • The list of required columns and each column’s expected data type
  • The list of allowed categorical values (for example, which values metric may take)
  • Acceptable ranges and the maximum tolerable null ratio
  • Whether a given violation should be treated as a warning or a blocking error
  • A notification process and versioning rule for when the schema changes

Warning

A pipeline with no contract checks will not error when the data degrades, it will just quietly produce a wrong result. Failing early looks like "wasting one run," but it is far cheaper than letting a wrong result reach users.
Check yourself

The upstream data’s metric column contains a value the pipeline has never seen before, and it is not on the allowed list. Under data contract principles, what should the pipeline do?

The upstream data’s metric column contains a value the pipeline has never seen before, and it is not on the allowed list. Under data contract principles, what should the pipeline do?

Key takeaway: Completing the steps is not enough; the result must be verifiable, reproducible, and explainable.

Guided Lab: Refactor a Notebook into a Pipeline

70 min

Guided Lab: Refactor a Notebook into a Pipeline

Learning objective: with instructor guidance, build ingest→validate→transform→analyze→publish stages with checkpoints.

Info

Lab question: if you were out sick today and someone else had to run your monthly analysis, could your code let them safely rerun it without asking you a single question, and would they know where it might fail?
  1. Ingest
    Isolate the logic that reads raw data into its own stage that only loads data, with no cleaning or computation.
  2. Validate
    Apply the data contract check before data enters later processing, failing early on blocking violations.
  3. Transform
    Isolate cleaning, filtering, and type conversion into their own stage, keeping each function’s input/output explicit.
  4. Analyze
    Isolate aggregation and computation into their own stage, and add a checkpoint (such as a row count or a total) to confirm the result is reasonable.
  5. Publish
    Isolate writing the output to its final location into its own stage, and log the config and environment used for this run.
Minimum content the Guided Lab should include (fill in your own results)
ItemWhat to report
Five stagesThe ingest / validate / transform / analyze / publish functions you named
At least one checkpointThe row count or total you checked in the analyze stage, and whether it matched expectations
Config/secret handlingHow you pulled credentials and paths out of the code
Still needs human judgmentAt least one assumption or decision that still needs business confirmation
  • Copying notebook cells directly into functions, but forgetting to remove their dependency on global variables
  • Adding a validate stage that exists only in name, without actually applying any contract checks
  • Hardcoding config or secrets into the pipeline code instead of reading them from the environment
  • Adding no checkpoints at all, and judging success only by whether the pipeline errored, not whether the intermediate results are reasonable

Warning

"Split into several functions" does not equal "the pipeline is reliable." Every stage needs an explicit input/output and must be rerunnable on its own without rerunning earlier steps.

Done

Before finishing, ask yourself: could someone else understand roughly what this pipeline does and where it might fail just by reading your stage names and checkpoints?
Check yourself

You finished the Guided Lab and split the notebook into ingest/validate/transform/analyze/publish stages, but the validate stage is currently empty, it does not actually check any contract rules. Does this count as completing this lesson’s core skill?

You finished the Guided Lab and split the notebook into ingest/validate/transform/analyze/publish stages, but the validate stage is currently empty, it does not actually check any contract rules. Does this count as completing this lesson’s core skill?

Key takeaway: Completing the steps is not enough; the result must be verifiable, reproducible, and explainable.

What next?