dubs-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +280 -0
- package/CLAUDE.md +46 -0
- package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
- package/CURRENT_SESSION.md +171 -0
- package/CURRENT_SESSION_DRAW.md +516 -0
- package/MARCH_MADNESS_SURVIVOR.md +254 -0
- package/PANDA.md +166 -0
- package/Procfile +4 -0
- package/README.md +476 -0
- package/controllers/livescoresController.js +376 -0
- package/controllers/pickemController.js +554 -0
- package/controllers/survivorAdminController.js +887 -0
- package/controllers/survivorController.js +623 -0
- package/cron/oracleMonitor.js +77 -0
- package/cron/pickemOracleMonitor.js +73 -0
- package/data/jackpot-history.json +952 -0
- package/data/ncaaTeams.js +406 -0
- package/documentation/API_SECURITY_GUIDE.md +327 -0
- package/documentation/ARCADE_API.md +593 -0
- package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
- package/documentation/ARCADE_QUICKSTART.md +242 -0
- package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
- package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
- package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
- package/documentation/CLAIM_STATUS_FIX.md +67 -0
- package/documentation/CLI_TOOL_GUIDE.md +372 -0
- package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
- package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
- package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
- package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
- package/documentation/DATABASE_QUERIES.md +269 -0
- package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
- package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
- package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
- package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
- package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
- package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
- package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
- package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
- package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
- package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
- package/documentation/HEROKU_DEPLOYMENT.md +134 -0
- package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
- package/documentation/JACKPOT_API.md +521 -0
- package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
- package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
- package/documentation/JWT_QUICK_SETUP.md +268 -0
- package/documentation/JWT_TESTING_GUIDE.md +404 -0
- package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
- package/documentation/KEEPER_SETUP.md +206 -0
- package/documentation/KEEPER_STATE_MACHINE.md +423 -0
- package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
- package/documentation/LOCAL_VOTING_TEST.md +279 -0
- package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
- package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
- package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
- package/documentation/PNL_TRACKING_SETUP.md +189 -0
- package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
- package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
- package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
- package/documentation/QUICK_AUTH_SETUP.md +99 -0
- package/documentation/QUICK_DEPLOY.md +224 -0
- package/documentation/QUICK_FIX.md +114 -0
- package/documentation/QUICK_START.md +152 -0
- package/documentation/REFEREE_MODE_GUIDE.md +392 -0
- package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
- package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
- package/documentation/RUN_MIGRATION_NOW.md +39 -0
- package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
- package/documentation/SETUP_GUIDE.md +184 -0
- package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
- package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
- package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
- package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
- package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
- package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
- package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
- package/ecosystem.config.js +65 -0
- package/env.template +125 -0
- package/middleware/apiKeyAuth.js +136 -0
- package/middleware/authenticate.js +214 -0
- package/middleware/developerUserAuth.js +76 -0
- package/middleware/socketAuth.js +69 -0
- package/package.json +49 -0
- package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
- package/postman/Dubs-API-v1.postman_collection.json +205 -0
- package/postman/Dubs_Developer_API.postman_collection.json +662 -0
- package/postman/QUICKSTART.md +118 -0
- package/postman/QUICK_REFERENCE.md +246 -0
- package/postman/README.md +71 -0
- package/postman/VOTING_API_GUIDE.md +426 -0
- package/refactor/Animations.md +148 -0
- package/refactor/Chat.md +252 -0
- package/routes/actionsRoutes.js +699 -0
- package/routes/adminRoutes.js +370 -0
- package/routes/analyticsRoutes.js +1262 -0
- package/routes/arcadeRoutes.js +557 -0
- package/routes/authRoutes.js +2310 -0
- package/routes/avatarRoutes.js +85 -0
- package/routes/botRoutes.js +211 -0
- package/routes/chatRoutes.js +377 -0
- package/routes/cryptoPriceRoutes.js +105 -0
- package/routes/developerRoutes.js +4201 -0
- package/routes/deviceRoutes.js +214 -0
- package/routes/dmRoutes.js +167 -0
- package/routes/esportsRoutes.js +806 -0
- package/routes/exchangeRateRoutes.js +233 -0
- package/routes/gamesRoutes.js +3028 -0
- package/routes/jackpotRoutes.js +754 -0
- package/routes/keeperMonitoringRoutes.js +156 -0
- package/routes/keeperWebhookRoutes.js +466 -0
- package/routes/livescoresRoutes.js +31 -0
- package/routes/pickemAdminRoutes.js +199 -0
- package/routes/pickemRoutes.js +231 -0
- package/routes/playerStatsRoutes.js +147 -0
- package/routes/portfolioRoutes.js +217 -0
- package/routes/promoRoutes.js +418 -0
- package/routes/referralEarningsRoutes.js +392 -0
- package/routes/socialRoutes.js +459 -0
- package/routes/sportsRoutes.js +1271 -0
- package/routes/survivorAdminRoutes.js +345 -0
- package/routes/survivorRoutes.js +756 -0
- package/routes/uploadRoutes.js +256 -0
- package/routes/userProfileRoutes.js +244 -0
- package/routes/whatsNewRoutes.js +331 -0
- package/scripts/.claude/settings.local.json +15 -0
- package/scripts/README.md +170 -0
- package/scripts/RESTART_EVERYTHING.sh +104 -0
- package/scripts/add-claim-columns.sql +48 -0
- package/scripts/add-crypto-prices-cache.sql +27 -0
- package/scripts/add-exchange-rates-cache.sql +40 -0
- package/scripts/add-game-invite-column.sql +23 -0
- package/scripts/add-game-invite-notification.sql +33 -0
- package/scripts/add-game-invite-telegram-pref.sql +16 -0
- package/scripts/add-game-joined-notification.sql +16 -0
- package/scripts/add-game-joined-pref.js +40 -0
- package/scripts/add-game-joined-preference.sql +6 -0
- package/scripts/add-game-start-notifications.sql +41 -0
- package/scripts/add-notification-flags-to-games.sql +55 -0
- package/scripts/add-pending-game-dismissals.sql +19 -0
- package/scripts/add-preferred-currency.sql +34 -0
- package/scripts/add-winner-columns.js +61 -0
- package/scripts/add_mention_system.sql +53 -0
- package/scripts/add_payment_system.sql +96 -0
- package/scripts/add_sports_event_id_column.sql +22 -0
- package/scripts/analyze-cohort-data-heroku.js +276 -0
- package/scripts/analyze-cohort-data.js +295 -0
- package/scripts/analyze-prod-cohorts.sh +10 -0
- package/scripts/backfill-matchup-images.js +245 -0
- package/scripts/backfill-missing-signatures.js +175 -0
- package/scripts/backfill-referral-earnings.js +202 -0
- package/scripts/check-chat-schema.js +130 -0
- package/scripts/check-db.sh +14 -0
- package/scripts/check_oracle_in_game.js +54 -0
- package/scripts/cleanup-database.js +193 -0
- package/scripts/clear-notification-cache.js +85 -0
- package/scripts/convert-mnemonic.js +50 -0
- package/scripts/create-users-table.sql +44 -0
- package/scripts/debug-cohort-counts.js +248 -0
- package/scripts/debug-winner-calc.js +84 -0
- package/scripts/deploy-payment-system.sh +118 -0
- package/scripts/deploy-to-heroku.sh +63 -0
- package/scripts/diagnose-locked-round.js +143 -0
- package/scripts/dubs-cli.js +720 -0
- package/scripts/dump-account.js +65 -0
- package/scripts/find-vrf-offset.js +48 -0
- package/scripts/fix-chat-notifications-constraint.sql +122 -0
- package/scripts/fix-claim-columns.js +124 -0
- package/scripts/fix-constraint-now.js +44 -0
- package/scripts/fix-lock-timestamps.js +96 -0
- package/scripts/fix-locked-round.sh +126 -0
- package/scripts/fix-missing-badges.sql +91 -0
- package/scripts/fix-payment-notifications.sql +41 -0
- package/scripts/force-new-round.js +55 -0
- package/scripts/force-resolve-and-claim.js +278 -0
- package/scripts/important/README.md +115 -0
- package/scripts/important/authority-force-lock.js +197 -0
- package/scripts/important/authority-resolve-game.js +267 -0
- package/scripts/important/check-game-status.js +373 -0
- package/scripts/important/list-pending-games-by-version.js +270 -0
- package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
- package/scripts/initialize-jackpot.js +111 -0
- package/scripts/jackpot/.claude/settings.local.json +10 -0
- package/scripts/jackpot/force-reset.js +84 -0
- package/scripts/jackpot/initialize-mainnet.js +100 -0
- package/scripts/jackpot/keeper.js +742 -0
- package/scripts/jackpot/status.js +107 -0
- package/scripts/jackpot/update-round-duration.js +143 -0
- package/scripts/keeper-bot.js +112 -0
- package/scripts/list-pending-games.js +131 -0
- package/scripts/migrate-chat-v2.js +127 -0
- package/scripts/migrate-chat-winners.js +84 -0
- package/scripts/migrate-chat.sh +17 -0
- package/scripts/migrate-game-invite.js +83 -0
- package/scripts/migrate-heroku-game-notifications.sh +159 -0
- package/scripts/migrations/001_analytics_tables.sql +422 -0
- package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
- package/scripts/migrations/003_referral_earnings.sql +208 -0
- package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
- package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
- package/scripts/migrations/005_push_notifications.sql +55 -0
- package/scripts/migrations/006_add_draw_team_players.sql +28 -0
- package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
- package/scripts/migrations/007_add_gif_url.sql +8 -0
- package/scripts/migrations/008_add_connect4_columns.sql +139 -0
- package/scripts/migrations/008_add_pool_tracking.sql +22 -0
- package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
- package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
- package/scripts/migrations/011_create_developer_tables.sql +67 -0
- package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
- package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
- package/scripts/migrations/013_add_network_mode.sql +18 -0
- package/scripts/migrations/014_create_developer_app_users.sql +19 -0
- package/scripts/migrations/015_add_ui_config.sql +4 -0
- package/scripts/migrations/016_add_resolution_secret.sql +4 -0
- package/scripts/migrations/017_add_external_game_id.sql +3 -0
- package/scripts/migrations/018_create_pickem_tables.sql +115 -0
- package/scripts/migrations/019_expo_push_tokens.sql +19 -0
- package/scripts/migrations/create_whats_new_tables.sql +88 -0
- package/scripts/migrations/drop_live_games_tables.sql +34 -0
- package/scripts/open-jackpot-round.js +85 -0
- package/scripts/purge-all-data.sh +329 -0
- package/scripts/purge-all-data.sql +142 -0
- package/scripts/purge-heroku-data.sh +149 -0
- package/scripts/purge-heroku-data.sql +62 -0
- package/scripts/rebuild-heroku-database.sh +113 -0
- package/scripts/recover-funds.js +357 -0
- package/scripts/regenerate-epl-images.js +278 -0
- package/scripts/resize-s3-matchup-images.js +374 -0
- package/scripts/resolve-direct.js +88 -0
- package/scripts/resolve-mock-game.js +124 -0
- package/scripts/resolve-pickem-game.js +55 -0
- package/scripts/resolve-round-manual.js +83 -0
- package/scripts/resolve-stuck-game.js +382 -0
- package/scripts/resolve-stuck-round.js +42 -0
- package/scripts/run-connect4-migration.sh +16 -0
- package/scripts/run-mention-migration.sh +32 -0
- package/scripts/run-payment-migration.sh +51 -0
- package/scripts/run-preferred-currency-migration.sh +31 -0
- package/scripts/run-referral-earnings-migration.sh +32 -0
- package/scripts/run-survivor-outcome-migration.sh +16 -0
- package/scripts/seed-test-users.js +346 -0
- package/scripts/setup-auth-tables.js +78 -0
- package/scripts/setup-complete-database.sql +992 -0
- package/scripts/setup-database-fresh.sh +359 -0
- package/scripts/setup-heroku-keeper.sh +48 -0
- package/scripts/setup-keeper-database.js +83 -0
- package/scripts/setup-keeper-state-db.sql +110 -0
- package/scripts/setup-oracle.sh +39 -0
- package/scripts/setup-pnl-tracking.js +111 -0
- package/scripts/start-devnet.sh +14 -0
- package/scripts/test-arcade-devnet.sh +160 -0
- package/scripts/test-arcade-match.sh +109 -0
- package/scripts/test-automatic-mode.sh +239 -0
- package/scripts/test-connect4-cancel-claim.js +370 -0
- package/scripts/test-connect4-e2e.js +369 -0
- package/scripts/test-connect4-resolve.js +369 -0
- package/scripts/test-game-state-endpoint.js +136 -0
- package/scripts/test-invite-notification.js +86 -0
- package/scripts/test-jackpot-api.sh +71 -0
- package/scripts/test-poll-confirmation.js +267 -0
- package/scripts/test-resolve-game.js +271 -0
- package/scripts/test-resolve-signature.js +223 -0
- package/scripts/test-signature-preservation.js +124 -0
- package/scripts/test-state-machine.js +291 -0
- package/scripts/test-webhook-receiver.js +60 -0
- package/scripts/update-notification-constraint.js +52 -0
- package/scripts/verify-account-layout.js +145 -0
- package/scripts/verify-winner-algorithm.js +278 -0
- package/server.js +5259 -0
- package/services/arcadeMatchService.js +763 -0
- package/services/automaticGameOracle.js +1596 -0
- package/services/chatService.js +1612 -0
- package/services/connect4GameService.js +1049 -0
- package/services/connect4NotificationService.js +374 -0
- package/services/cryptoPriceService.js +223 -0
- package/services/customGameResolver.js +260 -0
- package/services/db.js +79 -0
- package/services/directMessageService.js +389 -0
- package/services/discordNotifications.js +160 -0
- package/services/exchangeRateService.js +289 -0
- package/services/expoPushService.js +314 -0
- package/services/gamesCacheService.js +539 -0
- package/services/jackpotHistory.js +331 -0
- package/services/jackpotService.js +856 -0
- package/services/keeperStateService.js +355 -0
- package/services/matchupImageService.js +591 -0
- package/services/notificationCacheService.js +407 -0
- package/services/pickemOracle.js +440 -0
- package/services/playerStatsService.js +389 -0
- package/services/portfolioService.js +555 -0
- package/services/promoService.js +757 -0
- package/services/promoTreasuryService.js +239 -0
- package/services/pushNotifications.js +353 -0
- package/services/redisService.js +422 -0
- package/services/referralEarningsService.js +728 -0
- package/services/s3Service.js +396 -0
- package/services/socialService.js +1202 -0
- package/services/survivorOracle.js +469 -0
- package/services/survivorSimulator.js +475 -0
- package/services/telegramNotifications.js +461 -0
- package/services/userProfileStatsService.js +1185 -0
- package/services/whatsNewService.js +388 -0
- package/utils/urlHelper.js +95 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort Retention Data Analysis - Heroku Production
|
|
3
|
+
*
|
|
4
|
+
* This script analyzes the production Heroku database.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* heroku config:get DATABASE_URL --app dubs-server-prod > /tmp/db_url.txt
|
|
8
|
+
* DATABASE_URL=$(cat /tmp/db_url.txt) node scripts/analyze-cohort-data-heroku.js
|
|
9
|
+
*
|
|
10
|
+
* Or use the Heroku CLI directly:
|
|
11
|
+
* heroku run node scripts/analyze-cohort-data-heroku.js --app dubs-server-prod
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { Pool } = require('pg');
|
|
15
|
+
|
|
16
|
+
async function analyzeCohortData() {
|
|
17
|
+
const DATABASE_URL = process.env.DATABASE_URL;
|
|
18
|
+
|
|
19
|
+
if (!DATABASE_URL) {
|
|
20
|
+
console.error('ā DATABASE_URL environment variable not set!');
|
|
21
|
+
console.error(' Set it with: export DATABASE_URL=<your-heroku-postgres-url>');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const isProduction = DATABASE_URL.includes('amazonaws') || DATABASE_URL.includes('heroku');
|
|
26
|
+
|
|
27
|
+
const pool = new Pool({
|
|
28
|
+
connectionString: DATABASE_URL,
|
|
29
|
+
ssl: isProduction ? { rejectUnauthorized: false } : false,
|
|
30
|
+
max: 2, // Use minimal connections for analysis
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log('\nš COHORT RETENTION DATA ANALYSIS (PRODUCTION)\n');
|
|
34
|
+
console.log('=' .repeat(80));
|
|
35
|
+
console.log(`Database: ${DATABASE_URL.split('@')[1]?.split('/')[0] || 'unknown'}`);
|
|
36
|
+
console.log('=' .repeat(80));
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// 1. Check for registration events
|
|
40
|
+
console.log('\nš 1. REGISTRATION EVENTS');
|
|
41
|
+
console.log('-'.repeat(80));
|
|
42
|
+
|
|
43
|
+
const registrationEvents = await pool.query(`
|
|
44
|
+
SELECT
|
|
45
|
+
log_type,
|
|
46
|
+
COUNT(*) as total_events,
|
|
47
|
+
COUNT(DISTINCT user_id) as unique_users,
|
|
48
|
+
MIN(created_at) as first_event,
|
|
49
|
+
MAX(created_at) as last_event
|
|
50
|
+
FROM audit_logs
|
|
51
|
+
WHERE log_type IN ('registration_started', 'registration_completed', 'registration_submitted')
|
|
52
|
+
GROUP BY log_type
|
|
53
|
+
ORDER BY total_events DESC
|
|
54
|
+
`);
|
|
55
|
+
|
|
56
|
+
if (registrationEvents.rows.length === 0) {
|
|
57
|
+
console.log('ā No registration events found in database!');
|
|
58
|
+
console.log(' Cannot build cohort analysis without registration data.');
|
|
59
|
+
await pool.end();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log('\nRegistration Events Summary:');
|
|
64
|
+
registrationEvents.rows.forEach(row => {
|
|
65
|
+
console.log(` ${row.log_type}:`);
|
|
66
|
+
console.log(` - Total Events: ${row.total_events}`);
|
|
67
|
+
console.log(` - Unique Users: ${row.unique_users}`);
|
|
68
|
+
console.log(` - Date Range: ${row.first_event?.toISOString().split('T')[0]} to ${row.last_event?.toISOString().split('T')[0]}`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// 2. Check date range and volume
|
|
72
|
+
console.log('\n\nš
2. SIGNUP DATE DISTRIBUTION (Last 30 days)');
|
|
73
|
+
console.log('-'.repeat(80));
|
|
74
|
+
|
|
75
|
+
const dailySignups = await pool.query(`
|
|
76
|
+
SELECT
|
|
77
|
+
DATE(created_at) as signup_date,
|
|
78
|
+
COUNT(*) as signups,
|
|
79
|
+
COUNT(DISTINCT user_id) as unique_users
|
|
80
|
+
FROM audit_logs
|
|
81
|
+
WHERE log_type = 'registration_completed'
|
|
82
|
+
AND user_id IS NOT NULL
|
|
83
|
+
GROUP BY DATE(created_at)
|
|
84
|
+
ORDER BY signup_date DESC
|
|
85
|
+
LIMIT 30
|
|
86
|
+
`);
|
|
87
|
+
|
|
88
|
+
if (dailySignups.rows.length === 0) {
|
|
89
|
+
console.log(' No signup data found!');
|
|
90
|
+
} else {
|
|
91
|
+
dailySignups.rows.forEach(row => {
|
|
92
|
+
console.log(` ${row.signup_date.toISOString().split('T')[0]}: ${row.signups} signups (${row.unique_users} unique users)`);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 3. Weekly cohort summary
|
|
97
|
+
console.log('\n\nš 3. WEEKLY COHORT SUMMARY');
|
|
98
|
+
console.log('-'.repeat(80));
|
|
99
|
+
|
|
100
|
+
const weeklyCohorts = await pool.query(`
|
|
101
|
+
SELECT
|
|
102
|
+
DATE_TRUNC('week', DATE(created_at)) as cohort_week,
|
|
103
|
+
COUNT(*) as signups,
|
|
104
|
+
COUNT(DISTINCT user_id) as unique_users
|
|
105
|
+
FROM audit_logs
|
|
106
|
+
WHERE log_type = 'registration_completed'
|
|
107
|
+
AND user_id IS NOT NULL
|
|
108
|
+
GROUP BY DATE_TRUNC('week', DATE(created_at))
|
|
109
|
+
ORDER BY cohort_week DESC
|
|
110
|
+
LIMIT 12
|
|
111
|
+
`);
|
|
112
|
+
|
|
113
|
+
console.log('\nLast 12 weeks of signups:');
|
|
114
|
+
if (weeklyCohorts.rows.length === 0) {
|
|
115
|
+
console.log(' No weekly cohort data found!');
|
|
116
|
+
} else {
|
|
117
|
+
console.log('\n Week Starting | Signups | Unique Users');
|
|
118
|
+
console.log(' ' + '-'.repeat(50));
|
|
119
|
+
weeklyCohorts.rows.forEach(row => {
|
|
120
|
+
const week = row.cohort_week.toISOString().split('T')[0];
|
|
121
|
+
console.log(` ${week} | ${row.signups.toString().padStart(4)} | ${row.unique_users.toString().padStart(4)}`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 4. Sample retention calculation
|
|
126
|
+
console.log('\n\nš¬ 4. SAMPLE COHORT RETENTION (First 4 weeks)');
|
|
127
|
+
console.log('-'.repeat(80));
|
|
128
|
+
|
|
129
|
+
const cohortRetention = await pool.query(`
|
|
130
|
+
WITH signups AS (
|
|
131
|
+
SELECT
|
|
132
|
+
user_id,
|
|
133
|
+
MIN(DATE(created_at)) as signup_date
|
|
134
|
+
FROM audit_logs
|
|
135
|
+
WHERE log_type = 'registration_completed'
|
|
136
|
+
AND user_id IS NOT NULL
|
|
137
|
+
GROUP BY user_id
|
|
138
|
+
),
|
|
139
|
+
weekly_cohorts AS (
|
|
140
|
+
SELECT
|
|
141
|
+
DATE_TRUNC('week', signup_date) as cohort_week,
|
|
142
|
+
user_id,
|
|
143
|
+
signup_date
|
|
144
|
+
FROM signups
|
|
145
|
+
),
|
|
146
|
+
user_activity AS (
|
|
147
|
+
SELECT
|
|
148
|
+
wc.cohort_week,
|
|
149
|
+
wc.user_id,
|
|
150
|
+
wc.signup_date,
|
|
151
|
+
DATE(al.created_at) as activity_date,
|
|
152
|
+
al.created_at - wc.signup_date as time_since_signup
|
|
153
|
+
FROM weekly_cohorts wc
|
|
154
|
+
LEFT JOIN audit_logs al ON al.user_id = wc.user_id
|
|
155
|
+
AND DATE(al.created_at) >= wc.signup_date
|
|
156
|
+
),
|
|
157
|
+
retention_calc AS (
|
|
158
|
+
SELECT
|
|
159
|
+
cohort_week,
|
|
160
|
+
COUNT(DISTINCT user_id) as total_users,
|
|
161
|
+
COUNT(DISTINCT CASE
|
|
162
|
+
WHEN time_since_signup >= INTERVAL '1 day'
|
|
163
|
+
AND time_since_signup < INTERVAL '2 days'
|
|
164
|
+
THEN user_id
|
|
165
|
+
END) as d1_users,
|
|
166
|
+
COUNT(DISTINCT CASE
|
|
167
|
+
WHEN time_since_signup >= INTERVAL '7 days'
|
|
168
|
+
AND time_since_signup < INTERVAL '8 days'
|
|
169
|
+
THEN user_id
|
|
170
|
+
END) as d7_users,
|
|
171
|
+
COUNT(DISTINCT CASE
|
|
172
|
+
WHEN time_since_signup >= INTERVAL '14 days'
|
|
173
|
+
AND time_since_signup < INTERVAL '15 days'
|
|
174
|
+
THEN user_id
|
|
175
|
+
END) as d14_users,
|
|
176
|
+
COUNT(DISTINCT CASE
|
|
177
|
+
WHEN time_since_signup >= INTERVAL '30 days'
|
|
178
|
+
AND time_since_signup < INTERVAL '31 days'
|
|
179
|
+
THEN user_id
|
|
180
|
+
END) as d30_users
|
|
181
|
+
FROM user_activity
|
|
182
|
+
GROUP BY cohort_week
|
|
183
|
+
)
|
|
184
|
+
SELECT
|
|
185
|
+
cohort_week,
|
|
186
|
+
total_users,
|
|
187
|
+
d1_users,
|
|
188
|
+
CASE WHEN total_users > 0
|
|
189
|
+
THEN ROUND(100.0 * d1_users / total_users, 1)
|
|
190
|
+
ELSE 0
|
|
191
|
+
END as d1_pct,
|
|
192
|
+
d7_users,
|
|
193
|
+
CASE WHEN total_users > 0
|
|
194
|
+
THEN ROUND(100.0 * d7_users / total_users, 1)
|
|
195
|
+
ELSE 0
|
|
196
|
+
END as d7_pct,
|
|
197
|
+
d14_users,
|
|
198
|
+
CASE WHEN total_users > 0
|
|
199
|
+
THEN ROUND(100.0 * d14_users / total_users, 1)
|
|
200
|
+
ELSE 0
|
|
201
|
+
END as d14_pct,
|
|
202
|
+
d30_users,
|
|
203
|
+
CASE WHEN total_users > 0
|
|
204
|
+
THEN ROUND(100.0 * d30_users / total_users, 1)
|
|
205
|
+
ELSE 0
|
|
206
|
+
END as d30_pct
|
|
207
|
+
FROM retention_calc
|
|
208
|
+
WHERE total_users > 0
|
|
209
|
+
ORDER BY cohort_week DESC
|
|
210
|
+
LIMIT 4
|
|
211
|
+
`);
|
|
212
|
+
|
|
213
|
+
if (cohortRetention.rows.length === 0) {
|
|
214
|
+
console.log(' ā Cannot calculate cohort retention - insufficient data');
|
|
215
|
+
} else {
|
|
216
|
+
console.log('\n Cohort Week | Users | D1 | D1% | D7 | D7% | D14 | D14% | D30 | D30%');
|
|
217
|
+
console.log(' ' + '-'.repeat(90));
|
|
218
|
+
cohortRetention.rows.forEach(row => {
|
|
219
|
+
const week = row.cohort_week.toISOString().split('T')[0];
|
|
220
|
+
const users = row.total_users.toString().padStart(5);
|
|
221
|
+
const d1 = row.d1_users.toString().padStart(5);
|
|
222
|
+
const d1pct = `${row.d1_pct}%`.padStart(5);
|
|
223
|
+
const d7 = row.d7_users.toString().padStart(5);
|
|
224
|
+
const d7pct = `${row.d7_pct}%`.padStart(5);
|
|
225
|
+
const d14 = row.d14_users.toString().padStart(5);
|
|
226
|
+
const d14pct = `${row.d14_pct}%`.padStart(5);
|
|
227
|
+
const d30 = row.d30_users.toString().padStart(5);
|
|
228
|
+
const d30pct = `${row.d30_pct}%`.padStart(5);
|
|
229
|
+
console.log(` ${week} | ${users} | ${d1} | ${d1pct} | ${d7} | ${d7pct} | ${d14} | ${d14pct} | ${d30} | ${d30pct}`);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
console.log('\n ā
Cohort retention calculation is WORKING!');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 5. Total stats
|
|
236
|
+
console.log('\n\nš 5. OVERALL STATISTICS');
|
|
237
|
+
console.log('-'.repeat(80));
|
|
238
|
+
|
|
239
|
+
const totalStats = await pool.query(`
|
|
240
|
+
SELECT
|
|
241
|
+
COUNT(DISTINCT user_id) as total_registered_users,
|
|
242
|
+
MIN(created_at) as first_registration,
|
|
243
|
+
MAX(created_at) as last_registration,
|
|
244
|
+
EXTRACT(DAY FROM MAX(created_at) - MIN(created_at)) as days_of_data
|
|
245
|
+
FROM audit_logs
|
|
246
|
+
WHERE log_type = 'registration_completed'
|
|
247
|
+
AND user_id IS NOT NULL
|
|
248
|
+
`);
|
|
249
|
+
|
|
250
|
+
if (totalStats.rows.length > 0) {
|
|
251
|
+
const stats = totalStats.rows[0];
|
|
252
|
+
console.log(`\n Total Registered Users: ${stats.total_registered_users}`);
|
|
253
|
+
console.log(` First Registration: ${stats.first_registration?.toISOString().split('T')[0]}`);
|
|
254
|
+
console.log(` Last Registration: ${stats.last_registration?.toISOString().split('T')[0]}`);
|
|
255
|
+
console.log(` Days of Data: ${Math.floor(stats.days_of_data)}`);
|
|
256
|
+
|
|
257
|
+
console.log('\nā
COHORT RETENTION ANALYSIS IS READY TO BUILD!');
|
|
258
|
+
console.log('\nš Next steps:');
|
|
259
|
+
console.log(' 1. Create /api/analytics/cohort-retention endpoint');
|
|
260
|
+
console.log(' 2. Add UI component to AnalyticsDashboard');
|
|
261
|
+
console.log(' 3. Add CSV export for community manager');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('\nā Error analyzing data:', error.message);
|
|
266
|
+
console.error(error);
|
|
267
|
+
} finally {
|
|
268
|
+
console.log('\n' + '='.repeat(80));
|
|
269
|
+
console.log('\n');
|
|
270
|
+
await pool.end();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Run analysis
|
|
275
|
+
analyzeCohortData().catch(console.error);
|
|
276
|
+
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort Retention Data Analysis
|
|
3
|
+
*
|
|
4
|
+
* This script analyzes the audit_logs table to determine if we have
|
|
5
|
+
* sufficient data to build cohort retention reports.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/analyze-cohort-data.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
require('dotenv').config();
|
|
12
|
+
const { pool } = require('../services/db');
|
|
13
|
+
|
|
14
|
+
async function analyzeCohortData() {
|
|
15
|
+
console.log('\nš COHORT RETENTION DATA ANALYSIS\n');
|
|
16
|
+
console.log('=' .repeat(80));
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// 1. Check for registration events
|
|
20
|
+
console.log('\nš 1. REGISTRATION EVENTS');
|
|
21
|
+
console.log('-'.repeat(80));
|
|
22
|
+
|
|
23
|
+
const registrationEvents = await pool.query(`
|
|
24
|
+
SELECT
|
|
25
|
+
log_type,
|
|
26
|
+
COUNT(*) as total_events,
|
|
27
|
+
COUNT(DISTINCT user_id) as unique_users,
|
|
28
|
+
MIN(created_at) as first_event,
|
|
29
|
+
MAX(created_at) as last_event
|
|
30
|
+
FROM audit_logs
|
|
31
|
+
WHERE log_type IN ('registration_started', 'registration_completed', 'registration_submitted')
|
|
32
|
+
GROUP BY log_type
|
|
33
|
+
ORDER BY total_events DESC
|
|
34
|
+
`);
|
|
35
|
+
|
|
36
|
+
if (registrationEvents.rows.length === 0) {
|
|
37
|
+
console.log('ā No registration events found in database!');
|
|
38
|
+
console.log(' Cannot build cohort analysis without registration data.');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log('\nRegistration Events Summary:');
|
|
43
|
+
registrationEvents.rows.forEach(row => {
|
|
44
|
+
console.log(` ${row.log_type}:`);
|
|
45
|
+
console.log(` - Total Events: ${row.total_events}`);
|
|
46
|
+
console.log(` - Unique Users: ${row.unique_users}`);
|
|
47
|
+
console.log(` - Date Range: ${row.first_event?.toISOString().split('T')[0]} to ${row.last_event?.toISOString().split('T')[0]}`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 2. Check date range and volume
|
|
51
|
+
console.log('\n\nš
2. SIGNUP DATE DISTRIBUTION');
|
|
52
|
+
console.log('-'.repeat(80));
|
|
53
|
+
|
|
54
|
+
const dailySignups = await pool.query(`
|
|
55
|
+
SELECT
|
|
56
|
+
DATE(created_at) as signup_date,
|
|
57
|
+
COUNT(*) as signups,
|
|
58
|
+
COUNT(DISTINCT user_id) as unique_users
|
|
59
|
+
FROM audit_logs
|
|
60
|
+
WHERE log_type = 'registration_completed'
|
|
61
|
+
AND user_id IS NOT NULL
|
|
62
|
+
GROUP BY DATE(created_at)
|
|
63
|
+
ORDER BY signup_date DESC
|
|
64
|
+
LIMIT 30
|
|
65
|
+
`);
|
|
66
|
+
|
|
67
|
+
console.log('\nLast 30 days of signups:');
|
|
68
|
+
if (dailySignups.rows.length === 0) {
|
|
69
|
+
console.log(' No signup data found!');
|
|
70
|
+
} else {
|
|
71
|
+
dailySignups.rows.forEach(row => {
|
|
72
|
+
console.log(` ${row.signup_date.toISOString().split('T')[0]}: ${row.signups} signups (${row.unique_users} unique users)`);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 3. Check for user activity patterns
|
|
77
|
+
console.log('\n\nš„ 3. USER ACTIVITY PATTERNS (Sample)');
|
|
78
|
+
console.log('-'.repeat(80));
|
|
79
|
+
|
|
80
|
+
const userActivity = await pool.query(`
|
|
81
|
+
WITH user_signups AS (
|
|
82
|
+
SELECT
|
|
83
|
+
user_id,
|
|
84
|
+
MIN(created_at) as signup_date
|
|
85
|
+
FROM audit_logs
|
|
86
|
+
WHERE log_type = 'registration_completed'
|
|
87
|
+
AND user_id IS NOT NULL
|
|
88
|
+
GROUP BY user_id
|
|
89
|
+
LIMIT 5
|
|
90
|
+
),
|
|
91
|
+
user_events AS (
|
|
92
|
+
SELECT
|
|
93
|
+
us.user_id,
|
|
94
|
+
us.signup_date,
|
|
95
|
+
DATE(al.created_at) as activity_date,
|
|
96
|
+
COUNT(*) as events_count,
|
|
97
|
+
COUNT(DISTINCT al.log_type) as unique_event_types,
|
|
98
|
+
EXTRACT(DAY FROM MIN(al.created_at) - us.signup_date) as days_since_signup
|
|
99
|
+
FROM user_signups us
|
|
100
|
+
LEFT JOIN audit_logs al ON al.user_id = us.user_id
|
|
101
|
+
AND al.created_at >= us.signup_date
|
|
102
|
+
GROUP BY us.user_id, us.signup_date, DATE(al.created_at)
|
|
103
|
+
ORDER BY us.user_id, activity_date
|
|
104
|
+
)
|
|
105
|
+
SELECT * FROM user_events
|
|
106
|
+
ORDER BY user_id, activity_date
|
|
107
|
+
LIMIT 50
|
|
108
|
+
`);
|
|
109
|
+
|
|
110
|
+
console.log('\nSample user activity (first 5 users):');
|
|
111
|
+
if (userActivity.rows.length === 0) {
|
|
112
|
+
console.log(' No user activity data found!');
|
|
113
|
+
} else {
|
|
114
|
+
let currentUser = null;
|
|
115
|
+
userActivity.rows.forEach(row => {
|
|
116
|
+
if (row.user_id !== currentUser) {
|
|
117
|
+
currentUser = row.user_id;
|
|
118
|
+
console.log(`\n User: ${row.user_id.substring(0, 8)}...`);
|
|
119
|
+
console.log(` Signup: ${row.signup_date.toISOString().split('T')[0]}`);
|
|
120
|
+
}
|
|
121
|
+
console.log(` Day ${row.days_since_signup}: ${row.events_count} events (${row.unique_event_types} types)`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 4. Check for referral/source tracking
|
|
126
|
+
console.log('\n\nšÆ 4. SOURCE/REFERRAL TRACKING');
|
|
127
|
+
console.log('-'.repeat(80));
|
|
128
|
+
|
|
129
|
+
const sourceTracking = await pool.query(`
|
|
130
|
+
SELECT
|
|
131
|
+
metadata->>'hasReferralCode' as has_referral,
|
|
132
|
+
metadata->>'referralCode' as referral_code,
|
|
133
|
+
COUNT(*) as count
|
|
134
|
+
FROM audit_logs
|
|
135
|
+
WHERE log_type = 'registration_completed'
|
|
136
|
+
AND user_id IS NOT NULL
|
|
137
|
+
GROUP BY metadata->>'hasReferralCode', metadata->>'referralCode'
|
|
138
|
+
ORDER BY count DESC
|
|
139
|
+
LIMIT 10
|
|
140
|
+
`);
|
|
141
|
+
|
|
142
|
+
console.log('\nReferral tracking in registrations:');
|
|
143
|
+
if (sourceTracking.rows.length === 0) {
|
|
144
|
+
console.log(' No referral data found in metadata');
|
|
145
|
+
} else {
|
|
146
|
+
sourceTracking.rows.forEach(row => {
|
|
147
|
+
console.log(` Has Referral: ${row.has_referral || 'unknown'}, Code: ${row.referral_code || 'none'} - ${row.count} users`);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 5. Test cohort query structure
|
|
152
|
+
console.log('\n\nš¬ 5. COHORT RETENTION FEASIBILITY TEST');
|
|
153
|
+
console.log('-'.repeat(80));
|
|
154
|
+
|
|
155
|
+
const cohortTest = await pool.query(`
|
|
156
|
+
WITH signups AS (
|
|
157
|
+
-- Get first registration event per user
|
|
158
|
+
SELECT
|
|
159
|
+
user_id,
|
|
160
|
+
MIN(DATE(created_at)) as signup_date
|
|
161
|
+
FROM audit_logs
|
|
162
|
+
WHERE log_type = 'registration_completed'
|
|
163
|
+
AND user_id IS NOT NULL
|
|
164
|
+
GROUP BY user_id
|
|
165
|
+
),
|
|
166
|
+
weekly_cohorts AS (
|
|
167
|
+
-- Group signups into weekly cohorts
|
|
168
|
+
SELECT
|
|
169
|
+
DATE_TRUNC('week', signup_date) as cohort_week,
|
|
170
|
+
user_id,
|
|
171
|
+
signup_date
|
|
172
|
+
FROM signups
|
|
173
|
+
),
|
|
174
|
+
user_activity AS (
|
|
175
|
+
-- Get all user activity after signup
|
|
176
|
+
SELECT
|
|
177
|
+
wc.cohort_week,
|
|
178
|
+
wc.user_id,
|
|
179
|
+
wc.signup_date,
|
|
180
|
+
DATE(al.created_at) as activity_date,
|
|
181
|
+
al.created_at - wc.signup_date as time_since_signup
|
|
182
|
+
FROM weekly_cohorts wc
|
|
183
|
+
LEFT JOIN audit_logs al ON al.user_id = wc.user_id
|
|
184
|
+
AND DATE(al.created_at) >= wc.signup_date
|
|
185
|
+
),
|
|
186
|
+
retention_calc AS (
|
|
187
|
+
-- Calculate retention metrics
|
|
188
|
+
SELECT
|
|
189
|
+
cohort_week,
|
|
190
|
+
COUNT(DISTINCT user_id) as total_users,
|
|
191
|
+
COUNT(DISTINCT CASE
|
|
192
|
+
WHEN time_since_signup >= INTERVAL '1 day'
|
|
193
|
+
AND time_since_signup < INTERVAL '2 days'
|
|
194
|
+
THEN user_id
|
|
195
|
+
END) as d1_users,
|
|
196
|
+
COUNT(DISTINCT CASE
|
|
197
|
+
WHEN time_since_signup >= INTERVAL '7 days'
|
|
198
|
+
AND time_since_signup < INTERVAL '8 days'
|
|
199
|
+
THEN user_id
|
|
200
|
+
END) as d7_users
|
|
201
|
+
FROM user_activity
|
|
202
|
+
GROUP BY cohort_week
|
|
203
|
+
)
|
|
204
|
+
SELECT
|
|
205
|
+
cohort_week,
|
|
206
|
+
total_users,
|
|
207
|
+
d1_users,
|
|
208
|
+
CASE WHEN total_users > 0
|
|
209
|
+
THEN ROUND(100.0 * d1_users / total_users, 1)
|
|
210
|
+
ELSE 0
|
|
211
|
+
END as d1_pct,
|
|
212
|
+
d7_users,
|
|
213
|
+
CASE WHEN total_users > 0
|
|
214
|
+
THEN ROUND(100.0 * d7_users / total_users, 1)
|
|
215
|
+
ELSE 0
|
|
216
|
+
END as d7_pct
|
|
217
|
+
FROM retention_calc
|
|
218
|
+
WHERE total_users > 0
|
|
219
|
+
ORDER BY cohort_week DESC
|
|
220
|
+
LIMIT 10
|
|
221
|
+
`);
|
|
222
|
+
|
|
223
|
+
console.log('\nSample cohort retention (last 10 weeks):');
|
|
224
|
+
if (cohortTest.rows.length === 0) {
|
|
225
|
+
console.log(' ā Cannot calculate cohort retention - insufficient data');
|
|
226
|
+
} else {
|
|
227
|
+
console.log('\n Cohort Week | Signups | D1 Users | D1% | D7 Users | D7%');
|
|
228
|
+
console.log(' ' + '-'.repeat(70));
|
|
229
|
+
cohortTest.rows.forEach(row => {
|
|
230
|
+
const week = row.cohort_week.toISOString().split('T')[0];
|
|
231
|
+
const signups = row.total_users.toString().padStart(7);
|
|
232
|
+
const d1 = row.d1_users.toString().padStart(8);
|
|
233
|
+
const d1pct = `${row.d1_pct}%`.padStart(4);
|
|
234
|
+
const d7 = row.d7_users.toString().padStart(8);
|
|
235
|
+
const d7pct = `${row.d7_pct}%`.padStart(4);
|
|
236
|
+
console.log(` ${week} | ${signups} | ${d1} | ${d1pct} | ${d7} | ${d7pct}`);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
console.log('\n ā
Cohort retention calculation is FEASIBLE!');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 6. Summary and recommendations
|
|
243
|
+
console.log('\n\nš 6. SUMMARY & RECOMMENDATIONS');
|
|
244
|
+
console.log('-'.repeat(80));
|
|
245
|
+
|
|
246
|
+
const totalSignups = registrationEvents.rows.find(r => r.log_type === 'registration_completed');
|
|
247
|
+
const oldestSignup = dailySignups.rows[dailySignups.rows.length - 1];
|
|
248
|
+
const newestSignup = dailySignups.rows[0];
|
|
249
|
+
|
|
250
|
+
if (totalSignups && oldestSignup && newestSignup) {
|
|
251
|
+
console.log('\nā
DATA AVAILABILITY:');
|
|
252
|
+
console.log(` - Total Signups: ${totalSignups.total_events} events, ${totalSignups.unique_users} users`);
|
|
253
|
+
console.log(` - Date Range: ${oldestSignup.signup_date.toISOString().split('T')[0]} to ${newestSignup.signup_date.toISOString().split('T')[0]}`);
|
|
254
|
+
console.log(` - Cohort Analysis: POSSIBLE`);
|
|
255
|
+
|
|
256
|
+
console.log('\nš RECOMMENDED COHORT GROUPINGS:');
|
|
257
|
+
console.log(' - Weekly cohorts (best for initial analysis)');
|
|
258
|
+
console.log(' - Monthly cohorts (for long-term trends)');
|
|
259
|
+
console.log(' - Daily cohorts (if high volume)');
|
|
260
|
+
|
|
261
|
+
console.log('\nš METRICS WE CAN TRACK:');
|
|
262
|
+
console.log(' ā
signup_cohort (week/month starting)');
|
|
263
|
+
console.log(' ā
signup_date_range (date range string)');
|
|
264
|
+
console.log(' ā
total_signups (count)');
|
|
265
|
+
console.log(' ā
d1_users, d1_pct (day 1 retention)');
|
|
266
|
+
console.log(' ā
d7_users, d7_pct (day 7 retention)');
|
|
267
|
+
console.log(' ā
d14_users, d14_pct (day 14 retention)');
|
|
268
|
+
console.log(' ā
d30_users, d30_pct (day 30 retention)');
|
|
269
|
+
console.log(sourceTracking.rows.length > 0 ? ' ā
source (from referral tracking)' : ' ā ļø source (limited/no tracking)');
|
|
270
|
+
|
|
271
|
+
console.log('\nš NEXT STEPS:');
|
|
272
|
+
console.log(' 1. Create /api/analytics/cohort-retention endpoint');
|
|
273
|
+
console.log(' 2. Support weekly and monthly cohort grouping');
|
|
274
|
+
console.log(' 3. Add source/referral filter if available');
|
|
275
|
+
console.log(' 4. Build visualization in AnalyticsDashboard');
|
|
276
|
+
console.log(' 5. Export to CSV for community manager');
|
|
277
|
+
} else {
|
|
278
|
+
console.log('\nā INSUFFICIENT DATA:');
|
|
279
|
+
console.log(' Cannot build cohort retention without registration events');
|
|
280
|
+
console.log(' Ensure registration_completed events are being tracked');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error('\nā Error analyzing data:', error.message);
|
|
285
|
+
console.error(error);
|
|
286
|
+
} finally {
|
|
287
|
+
console.log('\n' + '='.repeat(80));
|
|
288
|
+
console.log('\n');
|
|
289
|
+
await pool.end();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Run analysis
|
|
294
|
+
analyzeCohortData().catch(console.error);
|
|
295
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Analyze cohort retention data on production Heroku database
|
|
3
|
+
|
|
4
|
+
echo "š Analyzing cohort retention data on dubs-server-prod..."
|
|
5
|
+
echo ""
|
|
6
|
+
|
|
7
|
+
# Get the DATABASE_URL from Heroku and run the analysis
|
|
8
|
+
heroku config:get DATABASE_URL --app dubs-server-prod | \
|
|
9
|
+
xargs -I {} env DATABASE_URL={} node scripts/analyze-cohort-data-heroku.js
|
|
10
|
+
|