Skip to main content

Account Churn Risk Processor

Overview​

The Account Churn Risk processor identifies accounts at high risk of churning by analyzing subscription cancellation patterns over the past 30 days. When 50% or more of an account's subscription value is canceled, a churn risk pulse is created for proactive retention efforts.

Source File: queue-manager/services/projects/accountChurnRisk.js
Queue File: queue-manager/queues/projects/accountChurn.js
Execution: Part of projects cron (every 5 minutes)
Business Impact: CRITICAL - Customer retention

Processing Flow​

sequenceDiagram
participant CRON as Projects Cron
participant SVC as Churn Service
participant SUB as Subscriptions
participant PULSE as Projects Pulse
participant QUEUE as Churn Queue

CRON->>SVC: calculateChurnRisk()
SVC->>SUB: Aggregate canceled subscriptions<br/>(last 30 days)
SUB-->>SVC: Cancellation data by account

loop For Each Account
SVC->>SVC: Calculate risk ratio<br/>(canceled / total)

alt Risk >= 50%
SVC->>PULSE: Check existing pulse

alt No Recent Pulse
SVC->>QUEUE: Add to queue
QUEUE->>PULSE: Create pulse
end
end
end

Key Logic​

Churn Risk Calculation​

churnRisk = canceledAmount / totalSubscriptionValue;

// Example:
// Canceled: $500
// Total: $1000
// Risk: 0.5 (50%)

// Threshold: >= 50% triggers pulse

Time Window​

  • Period: Last 30 days
  • Canceled Field: Stripe timestamp (seconds)
  • Current Time: Math.floor(Date.now() / 1000)

Subscription Filtering​

Only managed subscriptions (excluding sites and listings):

MANAGED_SUBSCRIPTIONS.filter(sub => !['site', 'listings'].includes(sub));

MongoDB Aggregation Pipeline​

Stage 1: Match Canceled Subscriptions​

{
$match: {
status: 'canceled',
canceled_at: { $gt: thirtyDaysAgo },
'plan.metadata.product_type': { $in: MANAGED_SUBSCRIPTIONS }
}
}

Stage 2: Project Key Fields​

{
$project: {
main_acc: '$account',
canceled_at: { $toDate: { $multiply: ['$canceled_at', 1000] } },
amount: '$plan.amount'
}
}

Stage 3: Group by Account​

{
$group: {
_id: '$main_acc',
total_caceled_amount: { $sum: '$amount' },
canceled_subscriptions: { $push: '$_id' }
}
}

Stage 4: Calculate Total Active Value​

Uses $lookup to get active subscription costs for same account.

Stage 5: Calculate Risk Ratio​

{
$project: {
account_id: '$_id',
has_risk: {
$divide: [
'$total_caceled_amount',
{ $first: '$total.total_cost' }
]
},
canceled_subscriptions: 1
}
}

Stage 6: Filter High Risk​

{
$match: {
has_risk: {
$gte: 0.5;
}
}
}

Stage 7: Check Existing Pulses​

Prevents duplicate pulses within 31 days.

Pulse Structure​

{
account_id: ObjectId,
type: 'account_churn_risk',
status: 'pending',
metadata: {
risk_ratio: 0.65,
canceled_amount: 50000,
total_amount: 77000,
canceled_subscriptions: [...]
}
}

Collections Used​

Input: store-subscription

  • Queries canceled subscriptions in last 30 days
  • Queries active subscriptions for total value

Check: projects-pulse

  • Prevents duplicate pulses
  • Checks for recent (31 days) churn risk pulses

Output: projects-pulse (via queue)

  • Creates new churn risk pulses

Error Handling​

try {
// Aggregation and queue processing
} catch (error) {
logger.error({
initiator: 'QM/projects/accountChurnRisk',
error: error,
});
throw error;
}

Performance​

Execution Time: 2-5 seconds
Frequency: Every 5 minutes
Typical Results: 0-20 accounts per run


Complexity: MEDIUM
Lines of Code: 210

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM