@@ -62,30 +62,97 @@ actor BackgroundDataProcessor {
6262 teamInfo = ProviderTeamInfo ( id: 0 , name: " Individual Account " , provider: provider)
6363 }
6464
65- // Calculate current month for up-to-date spending data
65+ // Fetch all available months of data
6666 let calendar = Calendar . current
6767 let currentDate = Date ( )
68- let calendarMonth = calendar. component ( . month, from: currentDate) // 1-based (1-12)
69- let month = calendarMonth - 1 // Convert to 0-based for API (0-11)
70- let year = calendar. component ( . year, from: currentDate)
71-
72- logger
73- . info (
74- """
75- Requesting invoice data for current month \( month) / \( year) \
76- (Calendar month \( calendarMonth) -> API month \( month) )
77- """ )
78-
79- // Fetch invoice and usage data concurrently
80- // Use team ID from team info (or 0 for fallback)
81- async let invoiceTask = providerClient. fetchMonthlyInvoice (
82- authToken: authToken,
83- month: month,
84- year: year,
85- teamId: teamInfo. id == 0 ? nil : teamInfo. id) // Use nil for fallback team
68+ let currentYear = calendar. component ( . year, from: currentDate)
69+ let currentMonth = calendar. component ( . month, from: currentDate) // 1-based (1-12)
70+
71+ // Determine start date for historical data (e.g., 12 months back)
72+ let startDate = calendar. date ( byAdding: . month, value: - 12 , to: currentDate) ?? currentDate
73+ let startYear = calendar. component ( . year, from: startDate)
74+ let startMonth = calendar. component ( . month, from: startDate) // 1-based (1-12)
75+
76+ logger. info ( " Fetching historical data from \( startMonth) / \( startYear) to \( currentMonth) / \( currentYear) " )
77+
78+ // Collect all invoice tasks
79+ var invoiceTasks : [ Task < ProviderMonthlyInvoice ? , Never > ] = [ ]
80+ var yearMonth = ( year: startYear, month: startMonth)
81+
82+ // Get invoice cache for Cursor provider
83+ let invoiceCache = if provider == . cursor {
84+ await CursorInvoiceCache . shared
85+ } else {
86+ nil as CursorInvoiceCache ?
87+ }
88+
89+ while ( yearMonth. year < currentYear) || ( yearMonth. year == currentYear && yearMonth. month <= currentMonth) {
90+ let apiMonth = yearMonth. month - 1 // Convert to 0-based for API (0-11)
91+ let year = yearMonth. year
92+ let effectiveTeamId = teamInfo. id == 0 ? nil : teamInfo. id
93+
94+ let task = Task { ( ) -> ProviderMonthlyInvoice ? in
95+ // Check cache first for Cursor provider
96+ if let cache = invoiceCache,
97+ let cachedInvoice = await cache. getCachedInvoice ( month: apiMonth, year: year, teamId: effectiveTeamId) {
98+ logger. info ( " Using cached invoice for \( yearMonth. month) / \( year) : \( cachedInvoice. totalSpendingCents) cents " )
99+ return cachedInvoice
100+ }
101+
102+ // Fetch from API if not cached
103+ do {
104+ let invoice = try await providerClient. fetchMonthlyInvoice (
105+ authToken: authToken,
106+ month: apiMonth,
107+ year: year,
108+ teamId: effectiveTeamId)
109+ logger. info ( " Fetched invoice for \( yearMonth. month) / \( year) : \( invoice. totalSpendingCents) cents " )
110+
111+ // Cache the result for Cursor provider
112+ if let cache = invoiceCache {
113+ await cache. cacheInvoice ( invoice, month: apiMonth, year: year, teamId: effectiveTeamId)
114+ }
115+
116+ return invoice
117+ } catch {
118+ logger. warning ( " Failed to fetch invoice for \( yearMonth. month) / \( year) : \( error. localizedDescription) " )
119+ return nil
120+ }
121+ }
122+ invoiceTasks. append ( task)
123+
124+ // Move to next month
125+ if yearMonth. month == 12 {
126+ yearMonth = ( year: yearMonth. year + 1 , month: 1 )
127+ } else {
128+ yearMonth = ( year: yearMonth. year, month: yearMonth. month + 1 )
129+ }
130+ }
131+
132+ // Also fetch usage data concurrently
86133 async let usageTask = providerClient. fetchUsageData ( authToken: authToken)
87-
88- let invoice = try await invoiceTask
134+
135+ // Wait for all invoice tasks to complete
136+ var invoices : [ ProviderMonthlyInvoice ] = [ ]
137+ for task in invoiceTasks {
138+ if let invoice = await task. value {
139+ invoices. append ( invoice)
140+ }
141+ }
142+
143+ // Combine all invoices into a single invoice with all items
144+ let allItems = invoices. flatMap { $0. items }
145+
146+ // Use the most recent invoice's pricing description
147+ let latestInvoice = invoices. last ( where: { $0. totalSpendingCents > 0 } ) ?? invoices. last
148+
149+ let combinedInvoice = ProviderMonthlyInvoice (
150+ items: allItems,
151+ pricingDescription: latestInvoice? . pricingDescription,
152+ provider: provider,
153+ month: currentMonth - 1 , // API month (0-based)
154+ year: currentYear
155+ )
89156
90157 // Try to fetch usage data, but don't fail if it's unavailable
91158 let usage : ProviderUsageData
@@ -105,18 +172,18 @@ actor BackgroundDataProcessor {
105172 }
106173
107174 if provider == . claude {
108- logger. info ( " Claude: Invoice items: \( invoice . items. count) , total: \( invoice . totalSpendingCents) cents " )
175+ logger. info ( " Claude: Total invoice items across all months : \( combinedInvoice . items. count) , total: \( combinedInvoice . totalSpendingCents) cents " )
109176 logger. info ( " Claude: Usage data - current: \( usage. currentRequests) , max: \( usage. maxRequests ?? 0 ) " )
110- if let pricing = invoice . pricingDescription {
177+ if let pricing = combinedInvoice . pricingDescription {
111178 logger. info ( " Claude: Pricing description: \( pricing. description) " )
112179 }
113180 }
114181
115- logger. info ( " Completed background processing for \( provider. displayName) " )
182+ logger. info ( " Completed background processing for \( provider. displayName) - fetched \( invoices . count ) months of data " )
116183 return ProviderDataResult (
117184 userInfo: userInfo,
118185 teamInfo: teamInfo,
119- invoice: invoice ,
186+ invoice: combinedInvoice ,
120187 usage: usage)
121188 }
122189}
0 commit comments