Skip to content

Create Your First Sales Report in Python with Mercury

This page documents the exact notebook code from sales-report.ipynb.

The notebook is a report-style app (Markdown-first), with:

  • inline filters (Region + Time granularity)
  • computed KPIs
  • an Altair line chart (Revenue over time)
  • a short Conclusions section
Sales report created with Mercury

Try the live report:

🚀 Load interactive demo Hover to start

Your first cell imports exactly these packages:

import numpy as np
import pandas as pd
import altair as alt
import mercury as mr
  • mr is used for widgets and rich Markdown sections
  • altair is used for the interactive chart

Your notebook starts with a Markdown section that introduces the report:

## Sales in Q1, Q2 2025 📊
...

This is the key difference between reports and dashboards: the report is meant to be read top-to-bottom, like a document.


Your dataset is generated with NumPy and stored in a pandas DataFrame.

# Example sales DataFrame
rng = np.random.default_rng(7)
dates = pd.date_range("2025-01-01", "2025-06-30", freq="D")
regions = ["North", "South", "West", "East"]
channels = ["Online", "Retail", "Partners"]
products = ["Starter", "Pro", "Enterprise"]
n = 2500 # number of rows
df = pd.DataFrame(
{
"date": rng.choice(dates, size=n),
"region": rng.choice(regions, size=n, p=[0.28, 0.22, 0.25, 0.25]),
"channel": rng.choice(channels, size=n, p=[0.55, 0.35, 0.10]),
"product": rng.choice(products, size=n, p=[0.55, 0.35, 0.10]),
"units": rng.integers(1, 9, size=n),
}
)
# Simple pricing model
price_map = {"Starter": 49, "Pro": 129, "Enterprise": 399}
df["unit_price"] = df["product"].map(price_map).astype(float)
df["revenue"] = (df["units"] * df["unit_price"]).round(2)

In your notebook the widgets are created inline using position="inline":

region_sel = mr.MultiSelect(label="Region", choices=regions, value=regions, position="inline")
granularity = mr.Select(label="Time granularity", choices=["Day", "Week", "Month"], value="Week", position="inline")

These widgets control:

  • which regions are included
  • how time is aggregated in the chart

Related docs:

Your filtering logic is intentionally minimal — the report filters only by region:

# filter data
mask = (df["region"].isin(region_sel.value))
dff = df.loc[mask].copy()

Next, the notebook calculates the core report metrics:

total_revenue = float(dff["revenue"].sum())
total_units = int(dff["units"].sum())
orders = int(len(dff))
aov = (total_revenue / orders) if orders else 0.0

7. Dynamic report section with mr.Markdown(f"...")

Section titled “7. Dynamic report section with mr.Markdown(f"...")”

This is the “report” part: you render a summary as Markdown, using live values from widgets and KPIs.

mr.Markdown(
f"""
### Report scope
- **Regions:** {region_sel.value}
- **Granularity:** {granularity.value}
### Key results
- **Total revenue:** {total_revenue:,.0f}
- **Units sold:** {total_units:,.0f}
- **Orders:** {orders:,.0f}
- **Average order value (AOV):** {aov:,.2f}
"""
)

Why it’s useful:

  • you can keep the UI minimal
  • the output reads like a human report
  • numbers update automatically when filters change

Before the chart, your notebook includes this Markdown explanation:

The chart below aggregates the data by the selected time granularity and splits lines by region.

This keeps the report readable.

9. Build the Altair chart (time aggregation + revenue lines)

Section titled “9. Build the Altair chart (time aggregation + revenue lines)”

Your chart code:

  • derives period based on the granularity widget
  • aggregates revenue/units
  • plots revenue (fixed in this notebook)
dff["date"] = pd.to_datetime(dff["date"])
if granularity.value == "Day":
dff["period"] = dff["date"].dt.date.astype("datetime64[ns]")
elif granularity.value == "Week":
dff["period"] = dff["date"].dt.to_period("W").dt.start_time
else: # Month
dff["period"] = dff["date"].dt.to_period("M").dt.start_time
agg = (
dff.groupby(["period", "region"], as_index=False)
.agg(revenue=("revenue", "sum"), units=("units", "sum"))
.sort_values("period")
)
y_field = "revenue"
y_title = "Revenue"
chart = (
alt.Chart(agg)
.mark_line(point=True)
.encode(
x=alt.X("period:T", title=""),
y=alt.Y(f"{y_field}:Q", title=y_title),
color=alt.Color("region:N", title="Region"),
tooltip=[
alt.Tooltip("period:T", title="Period"),
alt.Tooltip("region:N", title="Region"),
alt.Tooltip("revenue:Q", title="Revenue", format=",.2f"),
alt.Tooltip("units:Q", title="Units", format=",.0f"),
],
)
.properties(height=320, width=700)
.interactive()
)

Your notebook renders the chart by returning the variable:

chart

Mercury automatically displays it in the report flow.

Your report ends with a conclusions section:

## Conclusions
Sales are very good

This is where you can add interpretation, notes, and next steps.