@@ -26,19 +26,19 @@ import { CostCenterAggregator, type CostCenterResult } from './pipeline/aggregat
2626import { OrganizationAggregator , type OrganizationResult } from './pipeline/aggregators/organizationAggregator'
2727import { UserUsageAggregator , type UserUsageResult } from './pipeline/aggregators/userUsageAggregator'
2828import {
29- BUSINESS_MONTHLY_AIC_INCLUDED_CREDITS ,
30- ENTERPRISE_MONTHLY_AIC_INCLUDED_CREDITS ,
3129 calculateLicenseSummary ,
3230 inferReportPlanScope ,
3331 type AicIncludedCreditsOverrides ,
3432} from './pipeline/aicIncludedCredits'
33+ import { resolveIncludedCreditsPolicy } from './pipeline/includedCreditsPolicy'
3534import { PRODUCT_BUDGET_COPILOT , PRODUCT_BUDGET_COPILOT_CLOUD_AGENT , PRODUCT_BUDGET_SPARK } from './pipeline/productClassification'
3635import { runPipeline } from './pipeline/runPipeline'
3736import type { ReportFormatMetadata } from './pipeline/reportAdapters'
3837import { runBudgetSimulation , type BudgetSimulationResult } from './utils/budgetSimulation'
3938import { EMPTY_BUDGET_VALUES , getDefaultBudgetValues , getUserSpendSegmentsByUsername , type BudgetField , type BudgetValues } from './utils/costManagementBudgets'
4039import { calculateIndividualPlanUpgradeRecommendation , getIndividualLicenseMonthlyCost } from './utils/individualPlanUpgrade'
4140import { normalizeSeatCount } from './utils/seatCounts'
41+ import { getReportMode , isNativeAiCreditsMode } from './utils/reportMode'
4242import { useAppVersionCheck } from './hooks/useAppVersionCheck'
4343
4444type Status = 'idle' | 'processing' | 'done'
@@ -116,25 +116,37 @@ function App() {
116116 includedCreditsOverrides : AicIncludedCreditsOverrides = { } ,
117117 onProgress ?: ( progressInfo : { rowsProcessed : number ; progressPercent : number } ) => void ,
118118 ) => {
119- const statsAggregator = new QuickStatsAggregator ( )
120- const contextAggregator = new ReportContextAggregator ( )
121- const dailyAggregator = new DailyUsageAggregator ( )
122- const modelAggregator = new ModelUsageAggregator ( )
123- const productAggregator = new ProductUsageAggregator ( )
124- const costCenterAggregator = new CostCenterAggregator ( )
125- const orgAggregator = new OrganizationAggregator ( )
126- const userAggregator = new UserUsageAggregator ( )
127-
128- const pipelineResult = await runPipeline ( file , [
129- statsAggregator ,
130- contextAggregator ,
131- dailyAggregator ,
132- modelAggregator ,
133- productAggregator ,
134- costCenterAggregator ,
135- orgAggregator ,
136- userAggregator ,
137- ] , {
119+ let statsAggregator ! : QuickStatsAggregator
120+ let contextAggregator ! : ReportContextAggregator
121+ let dailyAggregator ! : DailyUsageAggregator
122+ let modelAggregator ! : ModelUsageAggregator
123+ let productAggregator ! : ProductUsageAggregator
124+ let costCenterAggregator ! : CostCenterAggregator
125+ let orgAggregator ! : OrganizationAggregator
126+ let userAggregator ! : UserUsageAggregator
127+
128+ const pipelineResult = await runPipeline ( file , ( reportMetadata ) => {
129+ statsAggregator = new QuickStatsAggregator ( )
130+ contextAggregator = new ReportContextAggregator ( )
131+ dailyAggregator = new DailyUsageAggregator ( reportMetadata )
132+ modelAggregator = new ModelUsageAggregator ( reportMetadata )
133+ productAggregator = new ProductUsageAggregator ( reportMetadata )
134+ costCenterAggregator = new CostCenterAggregator ( reportMetadata )
135+ orgAggregator = new OrganizationAggregator ( reportMetadata )
136+ userAggregator = new UserUsageAggregator ( reportMetadata )
137+
138+ return [
139+ statsAggregator ,
140+ contextAggregator ,
141+ dailyAggregator ,
142+ modelAggregator ,
143+ productAggregator ,
144+ costCenterAggregator ,
145+ orgAggregator ,
146+ userAggregator ,
147+ ]
148+ } , {
149+ enableNativeAiCreditsProcessing : true ,
138150 includedCreditsOverrides,
139151 progressResolution : 500 ,
140152 onProgress,
@@ -156,8 +168,16 @@ function App() {
156168 }
157169 } , [ ] )
158170
171+ const reportMode = getReportMode ( reportMetadata )
172+ const isNativeAiCreditsReport = isNativeAiCreditsMode ( reportMode )
173+ const rangeStart = reportContext ?. startDate ?? null
174+ const rangeEnd = reportContext ?. endDate ?? null
175+ const includedCreditsPolicy = resolveIncludedCreditsPolicy ( reportMode , { startDate : rangeStart , endDate : rangeEnd } )
176+ const showOrganizationPromotionalDataDisclaimer = reportMode === 'transition-period-billing-preview'
177+ || includedCreditsPolicy . id === 'native-ai-credits-summer-promo'
178+
159179 const getDefaultSeatCounts = useCallback ( ( ) => {
160- const summary = calculateLicenseSummary ( userUsage ?. users ?? [ ] )
180+ const summary = calculateLicenseSummary ( userUsage ?. users ?? [ ] , includedCreditsPolicy )
161181 return {
162182 business : normalizeSeatCount (
163183 summary . rows . find ( ( row ) => row . label === 'Copilot Business' ) ?. users ?? 0 ,
@@ -168,7 +188,7 @@ function App() {
168188 0 ,
169189 ) ,
170190 }
171- } , [ userUsage ] )
191+ } , [ includedCreditsPolicy , userUsage ] )
172192
173193 const resetReportState = useCallback ( ( { status, fileName } : { status : Status ; fileName : string | null } ) => {
174194 setStatus ( status )
@@ -339,6 +359,11 @@ function App() {
339359 const handleApplyBudgetSimulation = useCallback ( async ( ) => {
340360 const file = currentFileRef . current
341361 if ( ! file ) return
362+ if ( isNativeAiCreditsReport ) {
363+ setBudgetSimulation ( null )
364+ setBudgetSimulationError ( 'Budget simulation is not available for native AI Credits reports yet.' )
365+ return
366+ }
342367
343368 const budgetReportUsers = userUsage ?. users ?? [ ]
344369 const hasBudgetOrganizationContext = budgetReportUsers . some ( ( user ) => user . organizations . length > 0 || user . costCenters . length > 0 )
@@ -424,6 +449,7 @@ function App() {
424449 budgetValues . productCopilot ,
425450 budgetValues . productSpark ,
426451 budgetValues . user ,
452+ isNativeAiCreditsReport ,
427453 resolveIncludedCreditOverrides ,
428454 seatOverrides ,
429455 userUsage ,
@@ -485,8 +511,6 @@ function App() {
485511
486512 const hasReport = status === 'done' && fileName !== null && reportMetadata !== null
487513 const showSeatConfirmation = hasReport && seatConfirmationPending
488- const rangeStart = reportContext ?. startDate ?? null
489- const rangeEnd = reportContext ?. endDate ?? null
490514 const reportUsers = userUsage ?. users ?? [ ]
491515 const hasOrganizationContext = reportUsers . some ( ( user ) => user . organizations . length > 0 || user . costCenters . length > 0 )
492516 const reportPlanScope = inferReportPlanScope ( reportUsers . length , hasOrganizationContext )
@@ -505,15 +529,18 @@ function App() {
505529 ? { business : effectiveBusinessSeats , enterprise : effectiveEnterpriseSeats }
506530 : undefined
507531 const includedAicPoolSize = reportPlanScope === 'organization'
508- ? ( effectiveBusinessSeats * BUSINESS_MONTHLY_AIC_INCLUDED_CREDITS ) + ( effectiveEnterpriseSeats * ENTERPRISE_MONTHLY_AIC_INCLUDED_CREDITS )
509- : calculateLicenseSummary ( reportUsers ) . totalIncludedAic
532+ ? ( effectiveBusinessSeats * includedCreditsPolicy . organizationPlans . business . monthlyIncludedCredits ) + ( effectiveEnterpriseSeats * includedCreditsPolicy . organizationPlans . enterprise . monthlyIncludedCredits )
533+ : calculateLicenseSummary ( reportUsers , includedCreditsPolicy ) . totalIncludedAic
510534
511535 const selectedUser = individualUser
512536 ?? ( selectedUsername && userUsage
513537 ? userUsage . users . find ( ( user ) => user . username === selectedUsername ) ?? null
514538 : null )
515539 const canShowSpendInsights = Boolean ( userUsage ) && ! isIndividualReport && reportUsers . length > 1
516- const visibleActiveView = activeView === 'spendInsights' && ! canShowSpendInsights ? 'overview' : activeView
540+ const visibleActiveView = ( activeView === 'spendInsights' && ! canShowSpendInsights )
541+ || ( isNativeAiCreditsReport && ( activeView === 'guide' || activeView === 'faq' ) )
542+ ? 'overview'
543+ : activeView
517544 const userNavActive = isIndividualReport
518545 ? visibleActiveView === 'userDetails'
519546 : visibleActiveView === 'users' || visibleActiveView === 'userDetails'
@@ -704,25 +731,29 @@ function App() {
704731 < span className = "whitespace-nowrap overflow-hidden text-ellipsis max-sm:sr-only" > Cost Management</ span >
705732 </ button >
706733
707- < hr className = "border-0 border-t border-border-default my-[6px]" />
708-
709- < button
710- type = "button"
711- className = { `${ sidebarItemBase } ${ visibleActiveView === 'guide' ? sidebarActive : sidebarInactive } ` }
712- onClick = { ( ) => setActiveView ( 'guide' ) }
713- >
714- < InfoIcon size = { 18 } className = "shrink-0" aria-hidden />
715- < span className = "whitespace-nowrap overflow-hidden text-ellipsis max-sm:sr-only" > Report Format</ span >
716- </ button >
717-
718- < button
719- type = "button"
720- className = { `${ sidebarItemBase } ${ visibleActiveView === 'faq' ? sidebarActive : sidebarInactive } ` }
721- onClick = { ( ) => setActiveView ( 'faq' ) }
722- >
723- < QuestionIcon size = { 18 } className = "shrink-0" aria-hidden />
724- < span className = "whitespace-nowrap overflow-hidden text-ellipsis max-sm:sr-only" > FAQ</ span >
725- </ button >
734+ { ! isNativeAiCreditsReport && (
735+ < >
736+ < hr className = "border-0 border-t border-border-default my-[6px]" />
737+
738+ < button
739+ type = "button"
740+ className = { `${ sidebarItemBase } ${ visibleActiveView === 'guide' ? sidebarActive : sidebarInactive } ` }
741+ onClick = { ( ) => setActiveView ( 'guide' ) }
742+ >
743+ < InfoIcon size = { 18 } className = "shrink-0" aria-hidden />
744+ < span className = "whitespace-nowrap overflow-hidden text-ellipsis max-sm:sr-only" > Report Format</ span >
745+ </ button >
746+
747+ < button
748+ type = "button"
749+ className = { `${ sidebarItemBase } ${ visibleActiveView === 'faq' ? sidebarActive : sidebarInactive } ` }
750+ onClick = { ( ) => setActiveView ( 'faq' ) }
751+ >
752+ < QuestionIcon size = { 18 } className = "shrink-0" aria-hidden />
753+ < span className = "whitespace-nowrap overflow-hidden text-ellipsis max-sm:sr-only" > FAQ</ span >
754+ </ button >
755+ </ >
756+ ) }
726757 </ nav >
727758 </ aside >
728759
@@ -740,6 +771,8 @@ function App() {
740771 reportPlanScope = { reportPlanScope }
741772 upgradeRecommendation = { individualUpgradeRecommendation }
742773 onAdjustSeatCounts = { reportPlanScope === 'organization' && ! isIndividualReport ? ( ) => setActiveView ( 'users' ) : undefined }
774+ reportMode = { reportMode }
775+ showOrganizationPromotionalDataDisclaimer = { showOrganizationPromotionalDataDisclaimer }
743776 />
744777 ) : visibleActiveView === 'models' ? (
745778 modelUsage && modelUsage . models . length > 0 ? (
@@ -749,6 +782,8 @@ function App() {
749782 isIndividualReport = { isIndividualReport }
750783 rangeStart = { rangeStart }
751784 rangeEnd = { rangeEnd }
785+ reportMode = { reportMode }
786+ showOrganizationPromotionalDataDisclaimer = { showOrganizationPromotionalDataDisclaimer }
752787 />
753788 </ div >
754789 ) : null
@@ -764,6 +799,8 @@ function App() {
764799 setSelectedUsername ( username )
765800 setActiveView ( 'userDetails' )
766801 } }
802+ reportMode = { reportMode }
803+ includedCreditsPolicy = { includedCreditsPolicy }
767804 />
768805 </ div >
769806 ) : visibleActiveView === 'userDetails' || ( visibleActiveView === 'users' && isIndividualReport ) ? (
@@ -774,16 +811,24 @@ function App() {
774811 showUsersBreadcrumb = { ! isIndividualReport }
775812 rangeStart = { rangeStart }
776813 rangeEnd = { rangeEnd }
814+ reportMode = { reportMode }
815+ includedCreditsPolicy = { includedCreditsPolicy }
816+ showOrganizationPromotionalDataDisclaimer = { showOrganizationPromotionalDataDisclaimer }
777817 onBackToUsers = { isIndividualReport ? undefined : ( ) => setActiveView ( 'users' ) }
778818 />
779819 </ div >
780820 ) : visibleActiveView === 'costCenters' ? (
781821 < div className = { viewContentClasses } >
782- < CostCentersView data = { costCenters ?? { costCenters : [ ] } } rangeStart = { rangeStart } />
822+ < CostCentersView
823+ data = { costCenters ?? { costCenters : [ ] } }
824+ rangeStart = { rangeStart }
825+ reportMode = { reportMode }
826+ showOrganizationPromotionalDataDisclaimer = { showOrganizationPromotionalDataDisclaimer }
827+ />
783828 </ div >
784829 ) : visibleActiveView === 'products' ? (
785830 < div className = { viewContentClasses } >
786- < ProductsView data = { productUsage ?? { products : [ ] } } />
831+ < ProductsView data = { productUsage ?? { products : [ ] } } reportMode = { reportMode } />
787832 </ div >
788833 ) : visibleActiveView === 'spendInsights' ? (
789834 < div className = { viewContentClasses } >
@@ -818,19 +863,26 @@ function App() {
818863 isApplyingBudgetSimulation = { isApplyingBudgetSimulation }
819864 onBudgetValueChange = { handleBudgetValueChange }
820865 onApplyBudgetSimulation = { handleApplyBudgetSimulation }
866+ reportMode = { reportMode }
867+ showOrganizationPromotionalDataDisclaimer = { showOrganizationPromotionalDataDisclaimer }
821868 />
822869 </ div >
823- ) : visibleActiveView === 'guide' ? (
870+ ) : ! isNativeAiCreditsReport && visibleActiveView === 'guide' ? (
824871 < div className = { viewContentClasses } >
825872 < ReportGuideView />
826873 </ div >
827- ) : visibleActiveView === 'faq' ? (
874+ ) : ! isNativeAiCreditsReport && visibleActiveView === 'faq' ? (
828875 < div className = { viewContentClasses } >
829876 < FaqView />
830877 </ div >
831878 ) : (
832879 < div className = { viewContentClasses } >
833- < OrganizationsView data = { orgs ?? { organizations : [ ] } } rangeStart = { rangeStart } />
880+ < OrganizationsView
881+ data = { orgs ?? { organizations : [ ] } }
882+ rangeStart = { rangeStart }
883+ reportMode = { reportMode }
884+ showOrganizationPromotionalDataDisclaimer = { showOrganizationPromotionalDataDisclaimer }
885+ />
834886 </ div >
835887 ) }
836888 </ main >
0 commit comments