declare const expansionOfferArray: readonly ["free_subscription", "free_loaner", "control"]; export type ExpansionOffer = (typeof expansionOfferArray)[number]; declare const statusArray: readonly ["assigned", "pending", "contacted", "payment_collected", "shipped", "subscription_created", "activated", "returned", "canceled", "expired"]; export type BillingCampaignExpansionStatus = (typeof statusArray)[number]; /** * The experiment record for the Multi-Device Retention Test — one row per client, written the first * time the offer is evaluated for them, `control` included so intent-to-treat analysis has a * denominator. * * `UNIQUE(clientId)` is doing real work here. It is simultaneously: * - the immutability guarantee the spec asks for (assignment cannot change once written), * - "one offer per account" from the legal copy, * - and the race guard, since concurrent first-exposures collide on the key rather than on a * read-then-write in application code. A duplicate-key error means somebody else won; re-read. * * Bucketing happens in LaunchDarkly, not here. The FE reports whichever variation LD resolved and * this row records it first-write-wins, so a later flag reconfiguration cannot move an enrolled * client between arms. * * `expansionOffer` is the internal vocabulary. Notion fixes the analytics contract as * `trial_variant ∈ {a, b, control}` and ChartMogul segments on it, so `free_subscription` → `a` and * `free_loaner` → `b` at exactly two boundaries: Stripe subscription metadata, and the * `trial_variant` property on analytics events. Nothing else should speak in letters. * * Three satellite tables hang off this one as the client progresses — address and order at submit, * device at activation. A missing row *means* that stage has not happened. */ export declare class BillingCampaignExpansion { id: number; /** FK to `billingCampaign.id` for the `device_expansion` row. */ offerId: number; clientId: number; userId: string | null; email: string | null; expansionOffer: ExpansionOffer; assignedAt: Date | null; status: BillingCampaignExpansionStatus; /** * The customer's *existing* tracker — the one that made them eligible. Known at exposure. * * Not to be confused with the expansion tracker's IMEI, which nobody knows until CS activates the * device and which lives on `billingCampaignExpansionDevice.imei`. */ qualifyingImei: string | null; qualifyingSubscriptionId: string | null; qualifyingModelFamily: string | null; qualifyingPlanId: string | null; /** Banner rendered on the dashboard. */ offerShownAt: Date | null; offerClickedAt: Date | null; /** ✕ on the banner. Server-side so it survives a device change and feeds the funnel analysis. */ offerDismissedAt: Date | null; /** Enrolment — the request lands in the CS queue at this point. */ offerSubmittedAt: Date | null; createdAt: Date | null; updatedAt: Date | null; } export {};