Pattern 10: Engagement Velocity Tracking
Intent
Measure the rate and direction of change in engagement metrics over time, detecting acceleration, deceleration, and momentum shifts that predict future outcomes better than static scores alone.
Also Known As
- Rate of Change Analysis
- Momentum Tracking
- Trend Velocity
- Engagement Momentum
- Behavioral Acceleration
Problem
Current scores don't tell you where things are headed.
Two families both score 65/100:
Martinez Family (Score: 65)
- 3 months ago: 76
- 2 months ago: 72
- 1 month ago: 68
- Now: 65
- Velocity: -3.7 points/month (declining)
Chen Family (Score: 65) - 3 months ago: 52 - 2 months ago: 57 - 1 month ago: 61 - Now: 65 - Velocity: +4.3 points/month (improving)
Same score, opposite trajectories.
Sarah needs to: - Martinez: Urgent intervention (rapid decline) - Chen: Maintain momentum (accelerating improvement)
Without velocity: - Can't distinguish declining from improving - Miss early warning signals (decline started 3 months ago) - Can't predict future state - No sense of urgency (scores same, but one is crisis)
Context
When this pattern applies:
- Historical data available (need multiple time points)
- Change matters as much as current state
- Want to predict future trajectories
- Different velocities require different responses
- Acceleration/deceleration signals important
When this pattern may not be needed:
- No historical data (brand new system)
- State changes so rapidly that velocity meaningless
- Only current state matters (velocity irrelevant to action)
- Very simple domains with binary states
Forces
Competing concerns:
1. Sensitivity vs Stability - High sensitivity = detect small changes quickly - But also amplifies noise (random fluctuations) - Balance: Use smoothing, require sustained trends
2. Short-term vs Long-term - Short-term velocity = responsive to recent changes - Long-term velocity = more stable, misses new trends - Balance: Calculate both, use context-appropriate
3. Simple vs Sophisticated - Simple: (current - previous) / time - Sophisticated: Regression, exponential smoothing, forecasting - Balance: Start simple, add sophistication as needed
4. Absolute vs Relative - Absolute: -5 points/month - Relative: -7% decline per month - Balance: Track both for different use cases
5. Individual vs Comparative - Individual: This family's velocity - Comparative: Velocity vs peer average - Balance: Both perspectives valuable
Solution
Track multiple velocity metrics over different time windows, detect acceleration/deceleration, and use velocity as key input to prioritization and intervention decisions.
Key velocity metrics:
1. Score Velocity (Primary)
velocity = (current_score - previous_score) / time_period
2. Acceleration
acceleration = current_velocity - previous_velocity
3. Interaction Velocity
velocity = interactions_this_period / interactions_previous_period
4. Risk Velocity
velocity = (current_risk - previous_risk) / time_period
5. Trajectory (Projected)
projected_score = current_score + (velocity × periods_ahead)
Structure
Velocity Tracking Tables
-- Store velocity calculations
CREATE TABLE engagement_velocity (
velocity_id INT PRIMARY KEY IDENTITY(1,1),
family_id INT NOT NULL,
-- Calculation metadata
calculation_date DATETIME2 DEFAULT GETDATE(),
time_period_days INT DEFAULT 30,
-- Score velocity
score_velocity DECIMAL(6,2), -- Points per period
score_velocity_percent DECIMAL(6,2), -- Percent change
score_acceleration DECIMAL(6,2), -- Change in velocity
-- Classification
velocity_class VARCHAR(20), -- 'rapid_decline', 'declining', 'stable', 'improving', 'rapid_improvement'
acceleration_class VARCHAR(20), -- 'decelerating', 'steady', 'accelerating'
-- Risk velocity
withdrawal_risk_velocity DECIMAL(6,2),
payment_risk_velocity DECIMAL(6,2),
-- Interaction velocity
interaction_count_current INT,
interaction_count_previous INT,
interaction_velocity DECIMAL(6,2), -- Ratio (current/previous)
-- Projections
projected_score_30d DECIMAL(5,2),
projected_score_90d DECIMAL(5,2),
projected_tier_30d VARCHAR(20),
-- Confidence
velocity_confidence DECIMAL(3,2), -- 0-1, based on data quality
CONSTRAINT FK_velocity_family FOREIGN KEY (family_id)
REFERENCES families(family_id),
CONSTRAINT UQ_family_velocity UNIQUE (family_id)
);
-- Historical velocity snapshots (for acceleration calculation)
CREATE TABLE velocity_history (
history_id INT PRIMARY KEY IDENTITY(1,1),
family_id INT NOT NULL,
snapshot_date DATETIME2 NOT NULL,
score_velocity DECIMAL(6,2),
interaction_velocity DECIMAL(6,2),
withdrawal_risk_velocity DECIMAL(6,2),
CONSTRAINT FK_vh_family FOREIGN KEY (family_id)
REFERENCES families(family_id)
);
CREATE INDEX IX_vh_family_date ON velocity_history(family_id, snapshot_date);
-- Indexes
CREATE INDEX IX_velocity_class ON engagement_velocity(velocity_class);
CREATE INDEX IX_rapid_decline ON engagement_velocity(score_velocity)
WHERE velocity_class = 'rapid_decline';
Implementation
Velocity Calculator
class VelocityCalculator {
constructor(db) {
this.db = db;
}
async calculateForFamily(familyId, timePeriodDays = 30) {
// Get score history
const scoreHistory = await this.getScoreHistory(familyId, timePeriodDays * 4); // 4x period for context
if (scoreHistory.length < 2) {
return null; // Not enough data
}
// Calculate score velocity
const current = scoreHistory[0];
const previous = scoreHistory[1];
const daysBetween = this.daysBetween(current.calculation_date, previous.calculation_date);
const scoreVelocity = (current.engagement_score - previous.engagement_score) / (daysBetween / timePeriodDays);
const scoreVelocityPercent = previous.engagement_score > 0 ?
(scoreVelocity / previous.engagement_score) * 100 : 0;
// Calculate acceleration (if we have 3+ points)
let acceleration = 0;
if (scoreHistory.length >= 3) {
const prevPrevious = scoreHistory[2];
const prevDays = this.daysBetween(previous.calculation_date, prevPrevious.calculation_date);
const previousVelocity = (previous.engagement_score - prevPrevious.engagement_score) / (prevDays / timePeriodDays);
acceleration = scoreVelocity - previousVelocity;
}
// Calculate risk velocity
const riskHistory = await this.getRiskHistory(familyId, timePeriodDays * 2);
let withdrawalRiskVelocity = 0;
let paymentRiskVelocity = 0;
if (riskHistory.length >= 2) {
const currentRisk = riskHistory[0];
const previousRisk = riskHistory[1];
const riskDays = this.daysBetween(currentRisk.assessment_date, previousRisk.assessment_date);
withdrawalRiskVelocity = (currentRisk.withdrawal_risk - previousRisk.withdrawal_risk) / (riskDays / timePeriodDays);
paymentRiskVelocity = (currentRisk.payment_risk - previousRisk.payment_risk) / (riskDays / timePeriodDays);
}
// Calculate interaction velocity
const interactionVelocity = await this.calculateInteractionVelocity(familyId, timePeriodDays);
// Classify velocity
const velocityClass = this.classifyVelocity(scoreVelocity);
const accelerationClass = this.classifyAcceleration(acceleration);
// Project future scores
const projected30d = current.engagement_score + (scoreVelocity * 1); // 1 period ahead
const projected90d = current.engagement_score + (scoreVelocity * 3); // 3 periods ahead
const projectedTier30d = this.predictTier(projected30d);
// Calculate confidence based on data consistency
const confidence = this.calculateConfidence(scoreHistory, scoreVelocity);
// Save velocity metrics
await this.saveVelocity(familyId, {
time_period_days: timePeriodDays,
score_velocity: scoreVelocity,
score_velocity_percent: scoreVelocityPercent,
score_acceleration: acceleration,
velocity_class: velocityClass,
acceleration_class: accelerationClass,
withdrawal_risk_velocity: withdrawalRiskVelocity,
payment_risk_velocity: paymentRiskVelocity,
interaction_count_current: interactionVelocity.current,
interaction_count_previous: interactionVelocity.previous,
interaction_velocity: interactionVelocity.ratio,
projected_score_30d: projected30d,
projected_score_90d: projected90d,
projected_tier_30d: projectedTier30d,
velocity_confidence: confidence
});
// Save to history for future acceleration calculations
await this.saveVelocityHistory(familyId, scoreVelocity, interactionVelocity.ratio, withdrawalRiskVelocity);
return {
familyId,
velocity: {
score: scoreVelocity,
scorePercent: scoreVelocityPercent,
acceleration: acceleration,
class: velocityClass,
accelerationClass: accelerationClass
},
risk: {
withdrawal: withdrawalRiskVelocity,
payment: paymentRiskVelocity
},
interactions: interactionVelocity,
projections: {
score30d: projected30d,
score90d: projected90d,
tier30d: projectedTier30d
},
confidence: confidence
};
}
async getScoreHistory(familyId, days) {
// In production, would query a metrics_history table
// For now, simplified
return await this.db.query(`
SELECT
engagement_score,
calculation_date
FROM family_engagement_metrics
WHERE family_id = ?
AND calculation_date >= DATE_SUB(NOW(), INTERVAL ? DAY)
ORDER BY calculation_date DESC
`, [familyId, days]);
}
async getRiskHistory(familyId, days) {
return await this.db.query(`
SELECT
withdrawal_risk,
payment_risk,
assessment_date
FROM risk_assessments
WHERE family_id = ?
AND assessment_date >= DATE_SUB(NOW(), INTERVAL ? DAY)
ORDER BY assessment_date DESC
`, [familyId, days]);
}
async calculateInteractionVelocity(familyId, timePeriodDays) {
const counts = await this.db.query(`
SELECT
SUM(CASE WHEN interaction_timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY) THEN 1 ELSE 0 END) as current_count,
SUM(CASE WHEN interaction_timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
AND interaction_timestamp < DATE_SUB(NOW(), INTERVAL ? DAY) THEN 1 ELSE 0 END) as previous_count
FROM interaction_log
WHERE family_id = ?
`, [timePeriodDays, timePeriodDays * 2, timePeriodDays, familyId]);
const c = counts[0];
const ratio = c.previous_count > 0 ? c.current_count / c.previous_count : 1.0;
return {
current: c.current_count,
previous: c.previous_count,
ratio: ratio,
change: c.current_count - c.previous_count
};
}
classifyVelocity(scoreVelocity) {
if (scoreVelocity <= -10) return 'rapid_decline';
if (scoreVelocity <= -5) return 'declining';
if (scoreVelocity >= 10) return 'rapid_improvement';
if (scoreVelocity >= 5) return 'improving';
return 'stable';
}
classifyAcceleration(acceleration) {
if (acceleration <= -3) return 'decelerating';
if (acceleration >= 3) return 'accelerating';
return 'steady';
}
predictTier(score) {
if (score >= 80) return 'Champions';
if (score >= 60) return 'Stable';
if (score >= 40) return 'Developing';
return 'Critical';
}
calculateConfidence(history, velocity) {
// Confidence based on:
// 1. Number of data points
// 2. Consistency of trend
// 3. Recency of data
const dataPointsScore = Math.min(1.0, history.length / 5); // Max confidence at 5+ points
// Check trend consistency
let consistentPoints = 0;
const expectedDirection = velocity > 0 ? 1 : -1;
for (let i = 1; i < history.length; i++) {
const change = history[i - 1].engagement_score - history[i].engagement_score;
if ((change > 0 && expectedDirection > 0) || (change < 0 && expectedDirection < 0)) {
consistentPoints++;
}
}
const consistencyScore = history.length > 1 ? consistentPoints / (history.length - 1) : 0;
// Weighted combination
return (dataPointsScore * 0.5) + (consistencyScore * 0.5);
}
daysBetween(date1, date2) {
return Math.abs((new Date(date1) - new Date(date2)) / (1000 * 60 * 60 * 24));
}
async saveVelocity(familyId, metrics) {
await this.db.query(`
INSERT INTO engagement_velocity (
family_id, time_period_days,
score_velocity, score_velocity_percent, score_acceleration,
velocity_class, acceleration_class,
withdrawal_risk_velocity, payment_risk_velocity,
interaction_count_current, interaction_count_previous, interaction_velocity,
projected_score_30d, projected_score_90d, projected_tier_30d,
velocity_confidence
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (family_id) DO UPDATE SET
time_period_days = EXCLUDED.time_period_days,
score_velocity = EXCLUDED.score_velocity,
score_velocity_percent = EXCLUDED.score_velocity_percent,
score_acceleration = EXCLUDED.score_acceleration,
velocity_class = EXCLUDED.velocity_class,
acceleration_class = EXCLUDED.acceleration_class,
withdrawal_risk_velocity = EXCLUDED.withdrawal_risk_velocity,
payment_risk_velocity = EXCLUDED.payment_risk_velocity,
interaction_count_current = EXCLUDED.interaction_count_current,
interaction_count_previous = EXCLUDED.interaction_count_previous,
interaction_velocity = EXCLUDED.interaction_velocity,
projected_score_30d = EXCLUDED.projected_score_30d,
projected_score_90d = EXCLUDED.projected_score_90d,
projected_tier_30d = EXCLUDED.projected_tier_30d,
velocity_confidence = EXCLUDED.velocity_confidence,
calculation_date = GETDATE()
`, [
familyId, metrics.time_period_days,
metrics.score_velocity, metrics.score_velocity_percent, metrics.score_acceleration,
metrics.velocity_class, metrics.acceleration_class,
metrics.withdrawal_risk_velocity, metrics.payment_risk_velocity,
metrics.interaction_count_current, metrics.interaction_count_previous, metrics.interaction_velocity,
metrics.projected_score_30d, metrics.projected_score_90d, metrics.projected_tier_30d,
metrics.velocity_confidence
]);
}
async saveVelocityHistory(familyId, scoreVelocity, interactionVelocity, withdrawalRiskVelocity) {
await this.db.query(`
INSERT INTO velocity_history (
family_id, snapshot_date,
score_velocity, interaction_velocity, withdrawal_risk_velocity
) VALUES (?, NOW(), ?, ?, ?)
`, [familyId, scoreVelocity, interactionVelocity, withdrawalRiskVelocity]);
}
async getRapidDecliners(limit = 10) {
return await this.db.query(`
SELECT
f.family_id,
f.family_name,
fem.engagement_score as current_score,
ev.score_velocity,
ev.score_acceleration,
ev.velocity_class,
ev.projected_score_30d,
ev.projected_tier_30d,
ev.velocity_confidence
FROM engagement_velocity ev
JOIN families f ON ev.family_id = f.family_id
JOIN family_engagement_metrics fem ON f.family_id = fem.family_id
WHERE ev.velocity_class IN ('rapid_decline', 'declining')
AND f.enrolled_current_semester = 1
ORDER BY ev.score_velocity ASC
LIMIT ?
`, [limit]);
}
async getRapidImprovers(limit = 10) {
return await this.db.query(`
SELECT
f.family_id,
f.family_name,
fem.engagement_score as current_score,
ev.score_velocity,
ev.score_acceleration,
ev.velocity_class,
ev.projected_score_30d,
ev.velocity_confidence
FROM engagement_velocity ev
JOIN families f ON ev.family_id = f.family_id
JOIN family_engagement_metrics fem ON f.family_id = fem.family_id
WHERE ev.velocity_class IN ('rapid_improvement', 'improving')
AND f.enrolled_current_semester = 1
ORDER BY ev.score_velocity DESC
LIMIT ?
`, [limit]);
}
async getAcceleratingDeclines(limit = 10) {
// Families where decline is accelerating (getting worse faster)
return await this.db.query(`
SELECT
f.family_id,
f.family_name,
fem.engagement_score as current_score,
ev.score_velocity,
ev.score_acceleration,
ev.acceleration_class,
ev.projected_score_30d,
ev.projected_tier_30d
FROM engagement_velocity ev
JOIN families f ON ev.family_id = f.family_id
JOIN family_engagement_metrics fem ON f.family_id = fem.family_id
WHERE ev.score_velocity < 0 -- Declining
AND ev.acceleration_class = 'decelerating' -- Getting worse
AND f.enrolled_current_semester = 1
ORDER BY ev.score_acceleration ASC
LIMIT ?
`, [limit]);
}
}
module.exports = VelocityCalculator;
Usage Example
const velocityCalc = new VelocityCalculator(db);
// Calculate velocity for one family
const result = await velocityCalc.calculateForFamily(187);
console.log(`
Velocity Analysis for Family ${result.familyId}:
SCORE MOMENTUM:
Current Velocity: ${result.velocity.score.toFixed(2)} points/month
Percentage: ${result.velocity.scorePercent.toFixed(1)}% per month
Classification: ${result.velocity.class}
Acceleration: ${result.velocity.acceleration.toFixed(2)} (${result.velocity.accelerationClass})
RISK TRENDS:
Withdrawal Risk Velocity: ${result.risk.withdrawal.toFixed(2)} points/month
Payment Risk Velocity: ${result.risk.payment.toFixed(2)} points/month
INTERACTION TRENDS:
Current Period: ${result.interactions.current} interactions
Previous Period: ${result.interactions.previous} interactions
Velocity Ratio: ${result.interactions.ratio.toFixed(2)}x
PROJECTIONS:
Score in 30 days: ${result.projections.score30d.toFixed(1)}
Score in 90 days: ${result.projections.score90d.toFixed(1)}
Projected Tier (30d): ${result.projections.tier30d}
CONFIDENCE: ${(result.confidence * 100).toFixed(0)}%
`);
// Get families requiring urgent attention (rapid decliners)
const decliners = await velocityCalc.getRapidDecliners(5);
console.log('\n=== RAPID DECLINERS (URGENT) ===');
decliners.forEach(d => {
console.log(`${d.family_name}: ${d.current_score.toFixed(1)} → ${d.projected_score_30d.toFixed(1)} (${d.score_velocity.toFixed(1)}/mo)`);
});
// Get families to celebrate (rapid improvers)
const improvers = await velocityCalc.getRapidImprovers(5);
console.log('\n=== RAPID IMPROVERS (CELEBRATE!) ===');
improvers.forEach(i => {
console.log(`${i.family_name}: Improving at ${i.score_velocity.toFixed(1)} points/month`);
});
// Get families where decline is accelerating (crisis developing)
const accelerating = await velocityCalc.getAcceleratingDeclines(5);
console.log('\n=== ACCELERATING DECLINES (CRISIS) ===');
accelerating.forEach(a => {
console.log(`${a.family_name}: Declining AND accelerating (${a.score_acceleration.toFixed(1)})`);
});
Variations
By Time Window
Short-term (7-14 days): - Catches recent changes quickly - More noise, less stable - Good for: Real-time monitoring
Medium-term (30-60 days): - Balanced view - Most commonly used - Good for: General monitoring
Long-term (90-180 days): - Stable, smooth trends - Misses recent changes - Good for: Strategic planning
By Calculation Method
Simple Difference:
velocity = (current - previous) / days
Linear Regression:
// Fit line through multiple points
velocity = slope_of_best_fit_line
Exponential Smoothing:
// Weight recent data more heavily
velocity = alpha * current_change + (1-alpha) * previous_velocity
By Application
Prioritization: - Rapid decliners = highest priority - Rapid improvers = celebrate/learn from - Stable = standard treatment
Forecasting: - Project future scores - Predict tier changes - Estimate resource needs
Alert Triggers: - Velocity threshold crossed - Acceleration detected - Trend reversal
Consequences
Benefits
1. Predictive power Velocity predicts churn better than static scores (Martinez at 65 declining vs Chen at 65 improving)
2. Early warning amplification Catch problems earlier than score alone (decline started 3 months ago)
3. Differentiated treatment Same score, different velocities = different interventions
4. Celebration targets Identify improving families to reinforce positive momentum
5. Resource forecasting "If current velocities continue, 12 families will enter Critical tier next month"
6. Intervention urgency Accelerating decline = higher urgency than steady decline
Costs
1. Data requirements Need historical data (minimum 2 time points, ideally 5+)
2. Complexity More metrics to track, explain, act on
3. Noise sensitivity Random fluctuations can create false velocity signals
4. Lag Velocity calculation lags reality by one period
5. Interpretation difficulty "Acceleration of -2.3" harder to understand than "score of 65"
Sample Code
Velocity-aware prioritization:
async function getVelocityPrioritizedQueue() {
// Families prioritized by combination of score and velocity
return await db.query(`
SELECT
f.family_id,
f.family_name,
fem.engagement_score,
ev.score_velocity,
ev.velocity_class,
ev.projected_score_30d,
-- Priority score: combine current state and momentum
(
(100 - fem.engagement_score) * 0.5 + -- Lower score = higher priority
ABS(ev.score_velocity) * 5 * 0.3 + -- Rapid change = higher priority
CASE
WHEN ev.velocity_class = 'rapid_decline' THEN 30
WHEN ev.velocity_class = 'declining' THEN 15
ELSE 0
END * 0.2
) as priority_score
FROM families f
JOIN family_engagement_metrics fem ON f.family_id = fem.family_id
JOIN engagement_velocity ev ON f.family_id = ev.family_id
WHERE f.enrolled_current_semester = 1
AND (
fem.engagement_score < 70
OR ev.velocity_class IN ('rapid_decline', 'declining')
)
ORDER BY priority_score DESC
LIMIT 20
`);
}
Known Uses
Homeschool Co-op Intelligence Platform - Velocity tracking implemented for all families - Discovered: Rapid decliners (velocity < -10) have 76% churn rate vs 23% overall - Velocity-based prioritization improved intervention success by 34%
Stock Market - Moving averages, momentum indicators - Velocity and acceleration fundamental to technical analysis
SaaS Product Analytics - User engagement trends - Feature adoption velocity - Churn prediction models use velocity as key feature
Healthcare - Patient deterioration scores track rate of decline - Early warning systems use velocity of vital signs
Related Patterns
Requires: - Pattern 6: Composite Health Scoring - need scores to calculate velocity - Historical data tracking
Enhances: - Pattern 7: Multi-Dimensional Risk Assessment - velocity improves risk accuracy - Pattern 8: Tier-Based Segmentation - velocity affects tier assignment - Pattern 9: Early Warning Signals - velocity changes trigger alerts
Enabled by this: - Pattern 11: Historical Pattern Matching - velocity patterns predict outcomes - Pattern 12: Risk Stratification Models - velocity as model feature - Pattern 15: Intervention Recommendation Engine - velocity determines urgency
References
Academic Foundations
- Box, George E.P., Gwilym M. Jenkins, Gregory C. Reinsel, and Greta M. Ljung (2015). Time Series Analysis: Forecasting and Control (5th ed.). Wiley. ISBN: 978-1118675021
- Hyndman, Rob J., and George Athanasopoulos (2021). Forecasting: Principles and Practice (3rd ed.). OTexts. https://otexts.com/fpp3/ - Free online textbook
- Shumway, Robert H., and David S. Stoffer (2017). Time Series Analysis and Its Applications (4th ed.). Springer. ISBN: 978-3319524511
- Hamilton, James D. (1994). Time Series Analysis. Princeton University Press. ISBN: 978-0691042893
Technical Analysis (Momentum)
- MACD (Moving Average Convergence Divergence): Appel, G. (2005). Technical Analysis: Power Tools for Active Investors. FT Press.
- RSI (Relative Strength Index): Wilder, J.W. (1978). New Concepts in Technical Trading Systems. Trend Research.
- Momentum Indicators: Murphy, J.J. (1999). Technical Analysis of the Financial Markets. New York Institute of Finance. ISBN: 978-0735200661
Healthcare Applications
- Vital Sign Trends: Tarassenko, L., et al. (2006). "Integrated monitoring and analysis for early warning of patient deterioration." British Journal of Anaesthesia 97(1): 64-68.
- Rate of Change in Clinical Data: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6371008/ - Early detection using rates
SaaS Analytics
- Product-Led Growth Metrics: https://openviewpartners.com/product-led-growth/ - Engagement velocity in PLG
- Engagement Scoring: https://www.pendo.io/glossary/product-engagement-score/ - Pendo's engagement framework
- Cohort Retention Analysis: https://amplitude.com/blog/cohort-retention-analysis - Velocity across cohorts
Practical Implementation
- Time Series in Python:
- statsmodels: https://www.statsmodels.org/stable/tsa.html - Time series analysis
- Prophet: https://facebook.github.io/prophet/ - Facebook's forecasting tool
- pandas Rolling Windows: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.rolling.html
- InfluxDB: https://www.influxdata.com/ - Time series database
- TimescaleDB: https://www.timescale.com/ - PostgreSQL extension for time series
Related Trilogy Patterns
- Pattern 9: Early Warning Signals - Velocity changes trigger warnings
- Pattern 11: Historical Pattern Matching - Velocity patterns predict outcomes
- Pattern 12: Risk Stratification Models - Velocity as model feature
- Pattern 15: Intervention Recommendation Engine - Velocity determines urgency
- Volume 3, Pattern 2: Contextual Scaffolding - User engagement patterns
Tools & Services
- Mixpanel Insights: https://mixpanel.com/blog/engagement-metrics/ - User engagement velocity
- Amplitude Behavioral Cohorts: https://help.amplitude.com/hc/en-us/articles/231881448-Behavioral-Cohorts - Track behavior changes
- Heap Auto-Capture: https://heap.io/ - Automatic event tracking for velocity analysis