Customer Success teams spend too much time digging through CRM records to figure out which renewals are coming up and which accounts are at risk. A dedicated renewal tracking dashboard built with Streamlit gives CS managers a single view of everything that matters: upcoming renewals, health scores, and risk indicators, all updated in real time.
Data Model¶
The dashboard needs three data inputs that most CRM systems already track:
| Data Source | Key Fields | Purpose |
|---|---|---|
| Contracts/Opportunities | Account, renewal date, ARR, contract length | Timeline and revenue at risk |
| Health Signals | Support tickets, NPS score, last CSM meeting | Calculate health score |
| Activity History | Last login, feature adoption, engagement | Behavioral risk signals |
Calculating Health Scores¶
A simple weighted health score combines multiple signals into a single 0-100 metric:
def calculate_health_score(account):
score = 0
weights = {
"nps": 25,
"support_tickets": 25,
"engagement": 25,
"usage_trend": 25,
}
# NPS contribution (scale -100 to 100 -> 0 to 25)
nps = account.get("nps_score", 0)
score += max(0, (nps + 100) / 200) * weights["nps"]
# Support ticket volume (fewer is better)
tickets_30d = account.get("tickets_last_30d", 0)
if tickets_30d == 0:
score += weights["support_tickets"]
elif tickets_30d <= 2:
score += weights["support_tickets"] * 0.7
elif tickets_30d <= 5:
score += weights["support_tickets"] * 0.4
else:
score += weights["support_tickets"] * 0.1
# Engagement (days since last CSM meeting)
days_since_meeting = account.get("days_since_last_meeting", 90)
if days_since_meeting <= 14:
score += weights["engagement"]
elif days_since_meeting <= 30:
score += weights["engagement"] * 0.7
elif days_since_meeting <= 60:
score += weights["engagement"] * 0.4
else:
score += weights["engagement"] * 0.1
# Usage trend
if account.get("usage_trending_up", False):
score += weights["usage_trend"]
elif account.get("usage_stable", False):
score += weights["usage_trend"] * 0.6
return round(score)
Building the Dashboard¶
The Streamlit app presents renewals in a format CS teams can act on immediately:
import streamlit as st
import pandas as pd
from datetime import date, timedelta
st.set_page_config(page_title="Renewal Tracker", layout="wide")
st.title("Renewal Tracking Dashboard")
# Load data (replace with your CRM query)
df = load_renewal_data()
# Top-level metrics
col1, col2, col3, col4 = st.columns(4)
renewing_90d = df[df["renewal_date"] <= date.today() + timedelta(days=90)]
col1.metric("Renewals Next 90 Days", len(renewing_90d))
col2.metric("ARR Up for Renewal", f"${renewing_90d['arr'].sum():,.0f}")
col3.metric("Avg Health Score", f"{df['health_score'].mean():.0f}/100")
at_risk = df[df["health_score"] < 50]
col4.metric("At-Risk Accounts", len(at_risk))
Risk Indicators and Filtering¶
Color-coded risk levels make it easy to spot trouble:
def risk_level(score):
if score >= 75:
return "Healthy"
elif score >= 50:
return "Monitor"
return "At Risk"
df["risk_level"] = df["health_score"].apply(risk_level)
# Filter controls
st.sidebar.header("Filters")
risk_filter = st.sidebar.multiselect(
"Risk Level",
options=["Healthy", "Monitor", "At Risk"],
default=["Monitor", "At Risk"],
)
csm_filter = st.sidebar.multiselect(
"CSM", options=df["csm_name"].unique().tolist()
)
filtered = df[df["risk_level"].isin(risk_filter)]
if csm_filter:
filtered = filtered[filtered["csm_name"].isin(csm_filter)]
# Display renewal table
st.subheader("Upcoming Renewals")
st.dataframe(
filtered[["account_name", "csm_name", "arr", "renewal_date",
"health_score", "risk_level"]]
.sort_values("renewal_date"),
use_container_width=True,
)
Renewal Timeline Chart¶
A visual timeline shows the distribution of renewals across the quarter:
st.subheader("Renewal Timeline")
timeline = df.groupby(
pd.Grouper(key="renewal_date", freq="W")
)["arr"].sum().reset_index()
st.bar_chart(timeline.set_index("renewal_date"))
Running the Dashboard¶
streamlit run renewal_dashboard.py
Set up Streamlit’s auto-refresh or add st.cache_data(ttl=600) to your data loading function so the dashboard stays current without manual intervention.
Key Takeaways¶
- A composite health score built from three or four signals is better than no health score, even without product usage data
- Filtering by risk level lets CS managers focus their limited time on accounts most likely to churn
- A visual renewal timeline reveals clustering that could overwhelm the team during peak renewal periods
- Streamlit makes it possible to go from idea to working dashboard in an afternoon, no frontend engineering required