[codex] Add Stripe LTV metrics#2201
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds LTV metrics end-to-end: database columns and types, Stripe subscription end-date derivation and webhook/sync wiring, daily LTV computation in the insights trigger, admin trend/UI changes, two backfill CLI scripts, and unit tests. ChangesLTV Metrics Infrastructure
Sequence Diagram(s)sequenceDiagram
participant CLI as Backfill CLI
participant Supabase as Supabase DB/API
participant StripeAPI as Stripe API
participant FS as Filesystem
CLI->>Supabase: fetch `stripe_info` / `global_stats` (paginated)
Supabase-->>CLI: rows
CLI->>StripeAPI: fetch Subscription objects (concurrent)
StripeAPI-->>CLI: Subscription responses
CLI->>CLI: compute snapshots / estimate LTV
CLI->>Supabase: update `global_stats` / `stripe_info` for candidates
Supabase-->>CLI: update acknowledgements
CLI->>FS: write failure report (if any)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/backfill_ltv_metrics.ts`:
- Around line 139-161: estimateCustomerLtv is currently counting a minimum paid
period whenever getPaidStart falls back to created_at, which treats unpaid
trials as revenue; modify the logic so a paid-start is only used when there is
explicit evidence of payment. Specifically, update getPaidStart (or add a
pre-check in estimateCustomerLtv) to return null unless the row contains a
paid-indicator (e.g., first_paid_at / has_paid_invoice / successful payment
status field) and only then fall back to created_at; keep
getKnownSubscriptionEnd and effectiveEnd logic unchanged but ensure rows without
any paid-indicator are rejected early (return null) so unpaid trials/failed
payments are not counted as revenue.
In `@scripts/backfill_stripe_subscription_end_dates.ts`:
- Around line 138-141: The current conditional only assigns
update.subscription_anchor_start/end when candidate.next_anchor_start/end are
truthy, so refresh mode (--refresh-existing) cannot clear anchors back to null;
change the checks for candidate.next_anchor_start and candidate.next_anchor_end
to test for the presence of the property (e.g., using "next_anchor_start" in
candidate or hasOwnProperty) and assign update.subscription_anchor_start =
candidate.next_anchor_start and update.subscription_anchor_end =
candidate.next_anchor_end even when those values are null so stale anchors get
cleared; update the logic around candidate -> update assignment in the backfill
loop where candidate.next_anchor_start / candidate.next_anchor_end are handled.
In `@src/pages/admin/dashboard/replication.vue`:
- Around line 111-112: The thrown Error for the missing session (check around
the session?.access_token guard) has an outdated message referencing a
replication secret; update the Error message to accurately reflect that the user
session is missing or expired and instruct operators to sign in or refresh the
session (e.g., "No active session: please sign in or refresh your authentication
token"); locate the guard that throws new Error(...) in the replication.vue
logic and replace the message string accordingly so it no longer mentions
replication secret fallback.
In `@supabase/functions/_backend/triggers/logsnag_insights.ts`:
- Around line 588-638: The source CTE is including trials/failed checkouts
because paid_start falls back to si.created_at; restrict rows to actual paid
subscriptions by adding a filter such as AND si.paid_at IS NOT NULL (or another
column that reliably indicates a successful payment) in the WHERE of the source
CTE so that the COALESCE(si.paid_at, si.subscription_anchor_start,
si.created_at) / paid_start logic only runs for records with real payments;
update the source CTE (referenced as source, paid_start, and the COALESCE
expression) to include this gating condition before ltv_values is computed.
In `@supabase/functions/_backend/utils/stripe_event.ts`:
- Line 69: The code sets data.subscription_ended_at using
getSubscriptionEndDate(subscription, firstItem) but getSubscriptionEndDate reads
item.current_period_end when cancel_at_period_end is true, and firstItem can be
the wrong line item for multi-item subscriptions; change the call to use the
licensed line item by passing currentLicensedItem instead of firstItem (ensure
currentLicensedItem is the same variable chosen at line 52) so
subscription_ended_at is derived from the licensed item's current_period_end via
getSubscriptionEndDate.
In `@supabase/functions/_backend/utils/stripe.ts`:
- Line 287: The current unconditional assignment
updateData.subscription_ended_at = subscriptionData?.endedAt ?? null wipes
existing end timestamps when subscriptionData is missing; change this to only
assign subscription_ended_at when subscriptionData actually provides an endedAt
value (i.e., check subscriptionData and subscriptionData.endedAt !== undefined)
so you don't overwrite previously stored timestamps on transient fetch
failures—use a conditional (e.g., if subscriptionData && typeof
subscriptionData.endedAt !== 'undefined') to set
updateData.subscription_ended_at, otherwise leave updateData untouched.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 58ad3341-1c44-44bc-8286-ef4f5e997b2d
📒 Files selected for processing (17)
package.jsonscripts/admin_stripe_backfill_utils.tsscripts/backfill_ltv_metrics.tsscripts/backfill_stripe_subscription_end_dates.tssrc/components/Sidebar.vuesrc/pages/admin/dashboard/replication.vuesrc/pages/admin/dashboard/revenue.vuesupabase/functions/_backend/triggers/logsnag_insights.tssupabase/functions/_backend/triggers/stripe_event.tssupabase/functions/_backend/utils/pg.tssupabase/functions/_backend/utils/postgres_schema.tssupabase/functions/_backend/utils/stripe.tssupabase/functions/_backend/utils/stripe_event.tssupabase/functions/_backend/utils/supabase.types.tssupabase/migrations/20260511101826_add_ltv_global_stats.sqltests/backfill-ltv-metrics.unit.test.tstests/backfill-stripe-subscription-end-dates.unit.test.ts
10972b0 to
1e9c630
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
1e9c630 to
b6a4255
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@supabase/functions/_backend/triggers/stripe_event.ts`:
- Around line 990-994: In cancelingOrFinished, prevent stale webhook updates
from overwriting canceled_at by first reading the row's last_stripe_event_at and
comparing it to the incoming event timestamp (from stripeData, e.g.,
stripeData.event_timestamp or stripeData.timestamp); only call
supabaseAdmin(c).from('stripe_info').update({ canceled_at: canceledAt
}).eq('customer_id', stripeData.customer_id) if the incoming event timestamp is
strictly newer than last_stripe_event_at on the DB row. Use the existing
canceledAt variable and perform a select for last_stripe_event_at before the
update, and skip the update when the DB timestamp is >= incoming event
timestamp.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: fcf7e2d4-a95a-493d-aa29-155624e52c40
📒 Files selected for processing (15)
package.jsonscripts/admin_stripe_backfill_utils.tsscripts/backfill_ltv_metrics.tsscripts/backfill_stripe_subscription_end_dates.tssrc/pages/admin/dashboard/revenue.vuesupabase/functions/_backend/triggers/logsnag_insights.tssupabase/functions/_backend/triggers/stripe_event.tssupabase/functions/_backend/utils/pg.tssupabase/functions/_backend/utils/stripe.tssupabase/functions/_backend/utils/stripe_event.tssupabase/functions/_backend/utils/supabase.types.tssupabase/migrations/20260511101826_add_ltv_global_stats.sqltests/backfill-ltv-metrics.unit.test.tstests/backfill-stripe-subscription-end-dates.unit.test.tstests/stripe-subscription-events.unit.test.ts
✅ Files skipped from review due to trivial changes (3)
- package.json
- tests/backfill-ltv-metrics.unit.test.ts
- tests/backfill-stripe-subscription-end-dates.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- supabase/functions/_backend/utils/pg.ts
- src/pages/admin/dashboard/revenue.vue
- supabase/functions/_backend/triggers/logsnag_insights.ts
- scripts/backfill_ltv_metrics.ts
- scripts/backfill_stripe_subscription_end_dates.ts
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
b6a4255 to
11ac8f2
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|



Summary (AI generated)
subscription_ended_atto Stripe subscription metadata stored in Supabase.Motivation (AI generated)
Capgo needs historical and ongoing LTV visibility in admin analytics. Persisting subscription end dates from Stripe makes churned subscription lifetime measurable without repeatedly querying Stripe, while global stats keeps dashboard reads cheap.
Business Impact (AI generated)
This gives the team clearer visibility into customer lifetime value distribution, helping revenue analysis, retention decisions, and plan performance tracking from the admin panel.
Test Plan (AI generated)
bun run lintbun run lint:backendbunx vitest run tests/backfill-ltv-metrics.unit.test.ts tests/backfill-stripe-subscription-end-dates.unit.test.ts tests/stripe-subscription-events.unit.test.ts tests/logsnag-insights-revenue.unit.test.tsbun run cli:build && vue-tsc --noEmitbun run buildSummary by CodeRabbit
New Features
New Scripts
Backend
Database Migration
Tests
Bug Fixes