Files
claude-skills-reference/marketing-skill/churn-prevention/scripts/churn_impact_calculator.py
Reza Rezvani 5add886197 fix: repair 25 Python scripts failing --help across all domains
- Fix Python 3.10+ syntax (float | None → Optional[float]) in 2 scripts
- Add argparse CLI handling to 9 marketing scripts using raw sys.argv
- Fix 10 scripts crashing at module level (wrap in __main__, add argparse)
- Make yaml/prefect/mcp imports conditional with stdlib fallbacks (4 scripts)
- Fix f-string backslash syntax in project_bootstrapper.py
- Fix -h flag conflict in pr_analyzer.py
- Fix tech-debt.md description (score → prioritize)

All 237 scripts now pass python3 --help verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 05:51:27 +01:00

200 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""Churn impact calculator — models revenue impact of churn reduction improvements."""
import json
import sys
SAMPLE_INPUT = {
"mrr": 50000,
"monthly_churn_rate_pct": 4.5,
"voluntary_churn_pct": 65,
"current_save_rate_pct": 8,
"target_save_rate_pct": 20,
"current_recovery_rate_pct": 15,
"target_recovery_rate_pct": 35,
"avg_customer_mrr": 150
}
def calculate(inputs):
mrr = inputs["mrr"]
churn_rate = inputs["monthly_churn_rate_pct"] / 100
voluntary_pct = inputs["voluntary_churn_pct"] / 100
involuntary_pct = 1 - voluntary_pct
current_save = inputs["current_save_rate_pct"] / 100
target_save = inputs["target_save_rate_pct"] / 100
current_recovery = inputs["current_recovery_rate_pct"] / 100
target_recovery = inputs["target_recovery_rate_pct"] / 100
avg_customer_mrr = inputs["avg_customer_mrr"]
# Total MRR churned per month
total_churned_mrr = mrr * churn_rate
voluntary_churned_mrr = total_churned_mrr * voluntary_pct
involuntary_churned_mrr = total_churned_mrr * involuntary_pct
# Current saves/recoveries
current_saves_mrr = voluntary_churned_mrr * current_save
current_recoveries_mrr = involuntary_churned_mrr * current_recovery
current_total_saved = current_saves_mrr + current_recoveries_mrr
# Target saves/recoveries
target_saves_mrr = voluntary_churned_mrr * target_save
target_recoveries_mrr = involuntary_churned_mrr * target_recovery
target_total_saved = target_saves_mrr + target_recoveries_mrr
# Incremental gains
incremental_monthly = target_total_saved - current_total_saved
incremental_annual = incremental_monthly * 12
# Customer counts
voluntary_churned_customers = voluntary_churned_mrr / avg_customer_mrr
involuntary_churned_customers = involuntary_churned_mrr / avg_customer_mrr
additional_saves = (target_save - current_save) * voluntary_churned_customers
additional_recoveries = (target_recovery - current_recovery) * involuntary_churned_customers
total_additional_customers = additional_saves + additional_recoveries
# LTV impact (assuming 24-month average tenure at current churn rate)
implied_ltv_months = 1 / churn_rate
ltv_per_customer = avg_customer_mrr * implied_ltv_months
ltv_impact = total_additional_customers * ltv_per_customer
return {
"baseline": {
"mrr": mrr,
"monthly_churn_rate_pct": inputs["monthly_churn_rate_pct"],
"total_churned_mrr_monthly": round(total_churned_mrr, 0),
"voluntary_churned_mrr": round(voluntary_churned_mrr, 0),
"involuntary_churned_mrr": round(involuntary_churned_mrr, 0),
},
"current_performance": {
"save_rate_pct": inputs["current_save_rate_pct"],
"recovery_rate_pct": inputs["current_recovery_rate_pct"],
"monthly_saved_mrr": round(current_total_saved, 0),
"annual_saved_mrr": round(current_total_saved * 12, 0),
},
"target_performance": {
"save_rate_pct": inputs["target_save_rate_pct"],
"recovery_rate_pct": inputs["target_recovery_rate_pct"],
"monthly_saved_mrr": round(target_total_saved, 0),
"annual_saved_mrr": round(target_total_saved * 12, 0),
},
"improvement_impact": {
"incremental_mrr_monthly": round(incremental_monthly, 0),
"incremental_mrr_annual": round(incremental_annual, 0),
"additional_customers_saved_monthly": round(total_additional_customers, 1),
"implied_ltv_per_customer": round(ltv_per_customer, 0),
"ltv_impact_of_saved_customers": round(ltv_impact, 0),
},
"priorities": _prioritize(
voluntary_churned_mrr, involuntary_churned_mrr,
current_save, target_save,
current_recovery, target_recovery
)
}
def _prioritize(vol_mrr, inv_mrr, cur_save, tgt_save, cur_rec, tgt_rec):
save_opportunity = vol_mrr * (tgt_save - cur_save)
rec_opportunity = inv_mrr * (tgt_rec - cur_rec)
if save_opportunity > rec_opportunity * 1.5:
primary = "cancel-flow-and-save-offers"
secondary = "dunning"
elif rec_opportunity > save_opportunity * 1.5:
primary = "dunning-and-payment-recovery"
secondary = "cancel-flow"
else:
primary = "both-roughly-equal"
secondary = "start-with-dunning-easier-to-implement"
return {
"voluntary_save_opportunity_mrr": round(save_opportunity, 0),
"involuntary_recovery_opportunity_mrr": round(rec_opportunity, 0),
"recommendation": primary,
"note": secondary
}
def print_report(result):
b = result["baseline"]
cur = result["current_performance"]
tgt = result["target_performance"]
imp = result["improvement_impact"]
pri = result["priorities"]
print("\n" + "="*60)
print(" CHURN IMPACT CALCULATOR")
print("="*60)
print(f"\n📊 BASELINE")
print(f" MRR: ${b['mrr']:,.0f}")
print(f" Monthly churn rate: {b['monthly_churn_rate_pct']}%")
print(f" Total MRR churned/mo: ${b['total_churned_mrr_monthly']:,.0f}")
print(f" └─ Voluntary: ${b['voluntary_churned_mrr']:,.0f}")
print(f" └─ Involuntary: ${b['involuntary_churned_mrr']:,.0f}")
print(f"\n📉 CURRENT PERFORMANCE")
print(f" Save rate: {cur['save_rate_pct']}%")
print(f" Payment recovery rate: {cur['recovery_rate_pct']}%")
print(f" MRR saved monthly: ${cur['monthly_saved_mrr']:,.0f}")
print(f" MRR saved annually: ${cur['annual_saved_mrr']:,.0f}")
print(f"\n🎯 TARGET PERFORMANCE")
print(f" Save rate: {tgt['save_rate_pct']}%")
print(f" Payment recovery rate: {tgt['recovery_rate_pct']}%")
print(f" MRR saved monthly: ${tgt['monthly_saved_mrr']:,.0f}")
print(f" MRR saved annually: ${tgt['annual_saved_mrr']:,.0f}")
print(f"\n💰 INCREMENTAL IMPACT")
print(f" Additional MRR/month: ${imp['incremental_mrr_monthly']:,.0f}")
print(f" Additional MRR/year: ${imp['incremental_mrr_annual']:,.0f}")
print(f" Customers saved/month: {imp['additional_customers_saved_monthly']}")
print(f" Implied LTV/customer: ${imp['implied_ltv_per_customer']:,.0f}")
print(f" LTV impact: ${imp['ltv_impact_of_saved_customers']:,.0f}")
print(f"\n🔍 PRIORITY RECOMMENDATION")
print(f" Voluntary opportunity: ${pri['voluntary_save_opportunity_mrr']:,.0f}/mo")
print(f" Involuntary opportunity: ${pri['involuntary_recovery_opportunity_mrr']:,.0f}/mo")
print(f" Focus on: {pri['recommendation'].replace('-', ' ').title()}")
if pri['note']:
print(f" Note: {pri['note'].replace('-', ' ')}")
print("\n" + "="*60 + "\n")
def main():
import argparse
parser = argparse.ArgumentParser(
description="Churn impact calculator — models revenue impact of churn reduction improvements."
)
parser.add_argument(
"input_file", nargs="?", default=None,
help="JSON file with churn metrics (default: run with sample data)"
)
parser.add_argument(
"--json", action="store_true",
help="Output results as JSON"
)
args = parser.parse_args()
if args.input_file:
with open(args.input_file) as f:
inputs = json.load(f)
else:
print("No input file provided. Running with sample data...\n")
print("Sample input:")
print(json.dumps(SAMPLE_INPUT, indent=2))
inputs = SAMPLE_INPUT
result = calculate(inputs)
print_report(result)
if args.json:
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()