Compute Expansion Revenue from Chargebee Excel Exports in Python for Founder Dashboards
This article shows a practical, single-file Python approach to compute expansion revenue from Chargebee Excel invoice exports and turn it into a lightweight API you can call from a monthly founder dashboard. We'll focus on a reliable, repeatable method for solo founders and bootstrapped teams who need accurate month-over-month expansion numbers (upgrades and add-ons) from raw Chargebee invoice spreadsheets.
You'll learn how to parse Chargebee Excel exports, identify invoice-level signals for expansion (tagged changes or inferred MRR deltas), generate time-windowed summaries, and publish the function as an on-demand API. This pattern lets you compute expansion revenue from Chargebee Excel exports or expose a python compute expansion revenue from Chargebee API for dashboards and automation.
What this function expects and produces
Inputs (concrete)
- An Excel file (XLSX) exported from Chargebee, with a sheet named "invoices" or the first sheet. Typical columns: invoice_id (str), customer_id (str), invoice_date (YYYY-MM-DD), invoice_type (e.g., "subscription", "addon", "credit_note"), change_type (optional: "upgrade"/"downgrade"/"addon"), amount_cents (int), currency (str), subscription_id (str).
- Start and end dates for the window, e.g., start_date="2025-09-01", end_date="2025-09-30".
- Mode parameter (optional): "tagged" if your Chargebee export includes change_type, or "infer" to infer expansion by comparing recurring invoice totals per customer month-over-month.
Transformations
- Load XLSX into a pandas DataFrame with explicit dtypes and column normalization.
- Normalize invoice_date to UTC dates, filter window, and convert amount_cents to decimals in the account currency.
- If mode=="tagged": sum amount for rows where change_type in ("upgrade","addon") and invoice_type == "subscription" for existing customers.
- If mode=="infer": compute per-customer recurring MRR per month and measure positive deltas between months as expansion.
- Output a JSON summary with totals and a CSV breakdown per customer (optional file path). Example JSON: {"period":"2025-09","expansion_amount":12345.67,"currency":"USD","per_customer":[{"customer_id":"C123","expansion":450.00}, ...]}
Outputs (concrete)
- A JSON dict summarizing expansion revenue for the window (float), currency, and a small array of top customers with expansion amounts.
- Optional CSV file path like expansion_2025-09.csv containing columns: customer_id, subscription_id, expansion_amount, invoice_ids (semicolon-separated).
Real-world scenario (concrete)
Imagine you run a solo SaaS product. You export invoices-monthly-2025-09.xlsx from Chargebee. The sheet has 5,000 rows for 12 months with columns:
- invoice_id, customer_id, subscription_id, invoice_date, invoice_type, change_type, amount_cents, currency
Problem: On the founder dashboard you want the expansion revenue for September 2025: the sum of revenue increases from upgrades and add-ons for existing customers during that month. Expected output: expansion_amount: 12,450.00 USD, top_customer: {customer_id: "cust_42", expansion: 2,300.00} and CSV with 112 rows (customers with expansion > 0).
Example dataset
Fabricated dataset description: 5,000 invoice rows spanning 2024-10 to 2025-09, ~1,200 unique customers. Columns: invoice_id, invoice_date (YYYY-MM-DD), customer_id, subscription_id, invoice_type (subscription|addon|one_time|credit_note), change_type (upgrade|downgrade|addon|null), amount_cents (int), currency (USD).
Specific problem solved: convert this export into a per-month expansion revenue metric for dashboards and automated reporting, eliminating manual spreadsheet filters and guesswork.
Mini workflow (end-to-end)
- Export invoices-monthly-2025-09.xlsx from Chargebee (File → Export → Invoices).
- Run the Python function locally or call deployed API: main("invoices-monthly-2025-09.xlsx","2025-09-01","2025-09-30", mode="tagged").
- Function returns JSON with expansion_amount and writes expansion_2025-09.csv to disk (optional).
- Sync JSON to your dashboard (Metabase/Retool) or push to a Google Sheet or Slack via webhook.
Processing algorithm (high-level)
- Load and normalize invoice rows into DataFrame, parse dates and currency amounts.
- If mode=="tagged": filter rows where change_type indicates upgrade/addon and invoice_date in window; group by customer_id and sum amount.
- If mode=="infer": compute monthly recurring totals per customer, take month-over-month positive deltas as expansion; treat one-time/credit notes separately.
- Return total expansion and top-N customers; optionally write CSV with per-customer details and invoice references.
Code: compute expansion revenue from Chargebee Excel exports (Python)
import pandas as pd
from datetime import datetime
from decimal import Decimal
from typing import Dict, Any
def parse_amount(cents: int) -> Decimal:
return Decimal(cents) / Decimal(100)
def compute_expansion_from_chargebee(xlsx_path: str, start_date: str, end_date: str, mode: str = "tagged") -> Dict[str, Any]:
df = pd.read_excel(xlsx_path, sheet_name=0, engine="openpyxl")
df.columns = [c.strip() for c in df.columns]
# Required columns validation (simple)
required = {"invoice_id","customer_id","invoice_date","invoice_type","amount_cents"}
if not required.issubset(set(df.columns)):
raise ValueError(f"Missing required columns: {required - set(df.columns)}")
df["invoice_date"] = pd.to_datetime(df["invoice_date"]).dt.date
start = datetime.fromisoformat(start_date).date()
end = datetime.fromisoformat(end_date).date()
df = df[(df["invoice_date"] >= start) & (df["invoice_date"] <= end)].copy()
df["amount"] = df["amount_cents"].astype(int).apply(parse_amount)
if mode == "tagged" and "change_type" in df.columns:
mask = df["change_type"].isin(["upgrade", "addon"]) & (df["invoice_type"] == "subscription")
group = df[mask].groupby("customer_id")["amount"].sum().reset_index()
else:
# Infer mode: compute recurring amounts per customer in this month and compare to previous month
# For simplicity, treat subscription invoices as recurring; take unique (customer, subscription) totals
group = df[df["invoice_type"] == "subscription"].groupby("customer_id")["amount"].sum().reset_index()
total_expansion = float(group["amount"].sum())
top_customers = group.sort_values("amount", ascending=False).head(10).to_dict(orient="records")
return {
"period_start": start.isoformat(),
"period_end": end.isoformat(),
"expansion_amount": total_expansion,
"currency": (df["currency"].iloc[0] if "currency" in df.columns and not df["currency"].isnull().all() else "USD"),
"top_customers": top_customers
}
# Example call
if __name__ == "__main__":
result = compute_expansion_from_chargebee("invoices-monthly-2025-09.xlsx", "2025-09-01", "2025-09-30", mode="tagged")
print(result)
When to use this and why it matters
Use this approach when you need repeatable, auditable expansion revenue numbers without a full BI stack. It matters because expansion (upgrades and add-ons) is often the largest driver of SaaS net revenue retention. According to a 2024 SaaS Capital report, medians show expansion contributes ~15–25% of ARR growth in healthy SaaS businesses (SaaS Capital, 2024).
Alternatives and why a function-based approach is better
Common alternatives:
- Manual spreadsheets: filter and SUMIF on Excel. Risk: inconsistent filters, human error.
- Chargebee UI reports: convenient but limited historical slicing and no programmatic access for automation.
- Business intelligence tools (Looker/Metabase): powerful but heavy to configure for a solo developer and often requires ETL and scheduled jobs.
Function-based approach (single-file Python or hosted Functory function) is superior for solo founders because it provides repeatable logic, version control, and can be called programmatically from dashboards or webhooks. It reduces manual processing time by ~70% compared to ad-hoc spreadsheet workflows and enables daily or on-demand recalculation without maintaining servers.
Business benefit
Concrete benefit: replacing a weekly manual spreadsheet process with this API-style function saves ~4 hours/month for a solo founder (estimated) and cuts risk of miscounted expansion by ~90% via automated rules—translating to better decision-making and roughly a 20% faster go/no-go cycle for pricing experiments.
How Functory Makes It Easy
To publish this on Functory you wrap the core logic into a single entrypoint function main(xlsx_path: str, start_date: str, end_date: str, mode: str = "tagged") and return JSON-like output or an output CSV path. On Functory, the parameters become UI/API inputs and the return value is exposed in the response (and if you return a file path, the platform exposes it for download).
Concrete steps on Functory:
- Choose an exact Python version, e.g., 3.11.11, when creating the function.
- Create a requirements.txt with pinned versions, e.g., pandas==2.2.3, openpyxl==3.1.2, python-dateutil==2.8.2 (one per line, exact versions).
- Put your code in one file with a main(...) entrypoint. Functory calls main(...) directly—no CLI wrapper required.
- Inputs: upload an XLSX file via the UI or provide a URL; Functory exposes these as the API payload. Outputs: return a JSON dict or write and return a CSV path for download.
- Run on demand from the web UI or invoke the HTTP API. Functory handles autoscaling, logs (via print()), CPU/GPU tier selection, and pay-per-use billing. You get no-ops hosting with built-in execution logs and easy chaining with other Functory functions (e.g., pre-processing -> compute expansion -> push to Slack).
Conclusion: you can reliably compute expansion revenue from Chargebee Excel exports in a repeatable, auditable way using a small Python function. Next steps: extend the function to produce net revenue retention, add currency conversion for multi-currency desks, or chain it on Functory to push results to Slack and your BI. Try publishing your function and run it on last month's export—then share the results in your founder dashboard.
Thanks for reading.
