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