@@ -45,7 +45,9 @@ interface BillingPeriodUsageWithDailyRefreshParams {
4545 *
4646 * The two aggregates intentionally keep different predicates. Ledger totals
4747 * use both captured period bounds (or a reporting-time window), while refresh
48- * uses the captured period start plus a created-at day window.
48+ * membership is the captured period-start stamp alone — created-at only
49+ * buckets rows into days, clamped into the period (see
50+ * `computeDailyRefreshConsumed` for why).
4951 */
5052export async function computeBillingPeriodUsageWithDailyRefresh (
5153 params : BillingPeriodUsageWithDailyRefreshParams ,
@@ -64,11 +66,7 @@ export async function computeBillingPeriodUsageWithDailyRefresh(
6466 const dailyRefreshDollars = planDollars * DAILY_REFRESH_RATE * seats
6567 const refreshWindowActive = cap > refreshPeriodStart
6668 const refreshFilter = refreshWindowActive
67- ? and (
68- eq ( usageLog . billingPeriodStart , refreshPeriodStart ) ,
69- gte ( usageLog . createdAt , refreshPeriodStart ) ,
70- lt ( usageLog . createdAt , cap )
71- )
69+ ? eq ( usageLog . billingPeriodStart , refreshPeriodStart )
7270 : sql < boolean > `false`
7371 const ledgerPeriodFilter =
7472 billingPeriod . source === 'reporting'
@@ -81,22 +79,18 @@ export async function computeBillingPeriodUsageWithDailyRefresh(
8179 const sameCapturedPeriodStart =
8280 billingPeriod . source !== 'reporting' &&
8381 billingPeriod . start . getTime ( ) === refreshPeriodStart . getTime ( )
84- const reportingWindowContainsRefresh =
85- billingPeriod . source === 'reporting' &&
86- refreshPeriodStart >= billingPeriod . start &&
87- cap <= billingPeriod . end
8882 const scanFilter = ! refreshWindowActive
8983 ? ledgerPeriodFilter
9084 : sameCapturedPeriodStart
9185 ? eq ( usageLog . billingPeriodStart , billingPeriod . start )
92- : reportingWindowContainsRefresh
93- ? ledgerPeriodFilter
94- : or ( ledgerPeriodFilter , refreshFilter )
86+ : or ( ledgerPeriodFilter , refreshFilter )
9587
88+ const startEpoch = Math . floor ( refreshPeriodStart . getTime ( ) / 1000 )
89+ const capEpoch = Math . floor ( cap . getTime ( ) / 1000 )
9690 const rows = await executor
9791 . select ( {
9892 dayIndex :
99- sql < number > `FLOOR((EXTRACT(EPOCH FROM ${ usageLog . createdAt } ) - ${ Math . floor ( refreshPeriodStart . getTime ( ) / 1000 ) } ) / 86400)` . as (
93+ sql < number > `FLOOR((LEAST(GREATEST( EXTRACT(EPOCH FROM ${ usageLog . createdAt } ), ${ startEpoch } ), ${ capEpoch - 1 } ) - ${ startEpoch } ) / 86400)` . as (
10094 'day_index'
10195 ) ,
10296 ledgerTotal :
@@ -167,10 +161,17 @@ export async function computeDailyRefreshConsumed(
167161 throw new Error ( 'Billing period exceeds the supported annual bound' )
168162 }
169163
164+ // Membership mirrors the ledger sums exactly: the entity and period stamps
165+ // alone. Created-at only assigns the day bucket, clamped into the period —
166+ // a straggler row written after the rollover (billing attribution is frozen
167+ // at run start) is billed by the stamp-based close, so it must consume
168+ // refresh on the period's final day rather than fall out of the deduction.
169+ const startEpoch = Math . floor ( periodStart . getTime ( ) / 1000 )
170+ const capEpoch = Math . floor ( cap . getTime ( ) / 1000 )
170171 const rows = await executor
171172 . select ( {
172173 dayIndex :
173- sql < number > `FLOOR((EXTRACT(EPOCH FROM ${ usageLog . createdAt } ) - ${ Math . floor ( periodStart . getTime ( ) / 1000 ) } ) / 86400)` . as (
174+ sql < number > `FLOOR((LEAST(GREATEST( EXTRACT(EPOCH FROM ${ usageLog . createdAt } ), ${ startEpoch } ), ${ capEpoch - 1 } ) - ${ startEpoch } ) / 86400)` . as (
174175 'day_index'
175176 ) ,
176177 dayTotal : sum ( usageLog . cost ) . as ( 'day_total' ) ,
@@ -180,9 +181,7 @@ export async function computeDailyRefreshConsumed(
180181 and (
181182 eq ( usageLog . billingEntityType , billingEntity . type ) ,
182183 eq ( usageLog . billingEntityId , billingEntity . id ) ,
183- eq ( usageLog . billingPeriodStart , periodStart ) ,
184- gte ( usageLog . createdAt , periodStart ) ,
185- lt ( usageLog . createdAt , cap )
184+ eq ( usageLog . billingPeriodStart , periodStart )
186185 )
187186 )
188187 . groupBy ( sql `day_index` )
0 commit comments