@@ -22,12 +22,27 @@ import useConfiguration from '../../hooks/use-configuration';
2222
2323const logger = new Logger ( 'DocumentPanel' ) ;
2424
25- // Format the cost cell content based on whether it's a total row
26- const formatCostCell = ( item ) => {
27- if ( item . isTotal ) {
28- return < Box fontWeight = "bold" > { `${ item . note } : ${ item . cost } ` } </ Box > ;
25+ // Helper function to parse serviceApi key into context and service
26+ const parseServiceApiKey = ( serviceApiKey ) => {
27+ const parts = serviceApiKey . split ( '/' ) ;
28+ if ( parts . length >= 3 ) {
29+ const context = parts [ 0 ] ;
30+ const serviceApi = parts . slice ( 1 ) . join ( '/' ) ;
31+ return { context, serviceApi } ;
2932 }
30- return item . cost ;
33+ // Fallback for keys that don't follow the new format (less than 3 parts) - set context to ''
34+ return { context : '' , serviceApi : serviceApiKey } ;
35+ } ;
36+
37+ // Helper function to format cost cells
38+ const formatCostCell = ( rowItem ) => {
39+ if ( rowItem . isTotal ) {
40+ return < Box fontWeight = "bold" > { `${ rowItem . note } : ${ rowItem . cost } ` } </ Box > ;
41+ }
42+ if ( rowItem . isSubtotal ) {
43+ return < Box fontWeight = "bold" color = "text-body-secondary" > { `${ rowItem . note } : ${ rowItem . cost } ` } </ Box > ;
44+ }
45+ return rowItem . cost ;
3146} ;
3247
3348// Component to display metering information in a table
@@ -65,15 +80,18 @@ const MeteringTable = ({ meteringData, preCalculatedTotals }) => {
6580 return < Box > Loading pricing data...</ Box > ;
6681 }
6782
68- // Transform metering data into table rows
69- const tableItems = [ ] ;
83+ // Transform metering data into table rows with context parsing
84+ const rawTableItems = [ ] ;
85+ const contextTotals = { } ;
7086 let totalCost = 0 ;
7187
72- Object . entries ( meteringData ) . forEach ( ( [ serviceApi , metrics ] ) => {
88+ Object . entries ( meteringData ) . forEach ( ( [ originalServiceApiKey , metrics ] ) => {
89+ const { context, serviceApi } = parseServiceApiKey ( originalServiceApiKey ) ;
90+
7391 Object . entries ( metrics ) . forEach ( ( [ unit , value ] ) => {
7492 const numericValue = Number ( value ) ;
7593
76- // Look up the unit price from the pricing data
94+ // Look up the unit price from the pricing data using the parsed serviceApi
7795 let unitPrice = null ;
7896 let unitPriceDisplayValue = 'None' ;
7997 let cost = 0 ;
@@ -83,6 +101,13 @@ const MeteringTable = ({ meteringData, preCalculatedTotals }) => {
83101 unitPriceDisplayValue = `$${ unitPrice } ` ;
84102 cost = numericValue * unitPrice ;
85103 totalCost += cost ;
104+
105+ // Track context totals
106+ if ( ! contextTotals [ context ] ) {
107+ contextTotals [ context ] = 0 ;
108+ }
109+ contextTotals [ context ] += cost ;
110+
86111 logger . debug ( `Found price for ${ serviceApi } /${ unit } : ${ unitPriceDisplayValue } ` ) ;
87112 } else {
88113 logger . warn ( `Invalid price for ${ serviceApi } /${ unit } , using None` ) ;
@@ -91,34 +116,96 @@ const MeteringTable = ({ meteringData, preCalculatedTotals }) => {
91116 logger . debug ( `No price found for ${ serviceApi } /${ unit } , using None` ) ;
92117 }
93118
94- tableItems . push ( {
119+ rawTableItems . push ( {
120+ context,
95121 serviceApi,
96122 unit,
97123 value : String ( numericValue ) ,
98124 unitCost : unitPriceDisplayValue ,
99125 cost : unitPrice !== null ? `$${ cost . toFixed ( 4 ) } ` : 'N/A' ,
126+ costValue : cost ,
100127 isTotal : false ,
128+ isSubtotal : false ,
101129 } ) ;
102130 } ) ;
103131 } ) ;
104132
133+ // Group items by context and add subtotals
134+ const tableItems = [ ] ;
135+ const contextGroups = { } ;
136+
137+ // Group raw items by context
138+ rawTableItems . forEach ( ( item ) => {
139+ if ( ! contextGroups [ item . context ] ) {
140+ contextGroups [ item . context ] = [ ] ;
141+ }
142+ contextGroups [ item . context ] . push ( item ) ;
143+ } ) ;
144+
145+ // Sort contexts in specific order: OCR, Classification, Extraction, Summarization
146+ const contextOrder = [ 'BDAProject' , 'OCR' , 'Classification' , 'Extraction' , 'Summarization' ] ;
147+ const sortedContexts = Object . keys ( contextGroups ) . sort ( ( a , b ) => {
148+ const aIndex = contextOrder . indexOf ( a ) ;
149+ const bIndex = contextOrder . indexOf ( b ) ;
150+
151+ // If both contexts are in the predefined order, sort by their position
152+ if ( aIndex !== - 1 && bIndex !== - 1 ) {
153+ return aIndex - bIndex ;
154+ }
155+
156+ // If only one context is in the predefined order, it comes first
157+ if ( aIndex !== - 1 ) return - 1 ;
158+ if ( bIndex !== - 1 ) return 1 ;
159+
160+ // If neither context is in the predefined order, sort alphabetically
161+ return a . localeCompare ( b ) ;
162+ } ) ;
163+
164+ sortedContexts . forEach ( ( context ) => {
165+ // Add all items for this context
166+ tableItems . push ( ...contextGroups [ context ] ) ;
167+
168+ // Add subtotal row for this context
169+ const contextTotal = contextTotals [ context ] || 0 ;
170+ tableItems . push ( {
171+ context : '' ,
172+ serviceApi : '' ,
173+ unit : '' ,
174+ value : '' ,
175+ unitCost : '' ,
176+ cost : `$${ contextTotal . toFixed ( 4 ) } ` ,
177+ costValue : contextTotal ,
178+ isTotal : false ,
179+ isSubtotal : true ,
180+ note : `${ context } Subtotal` ,
181+ } ) ;
182+ } ) ;
183+
105184 // Use preCalculatedTotals if provided, otherwise calculate locally
106185 const finalTotalCost = preCalculatedTotals ? preCalculatedTotals . totalCost : totalCost ;
107186
108- // Add total row
187+ // Add overall total row
109188 tableItems . push ( {
189+ context : '' ,
110190 serviceApi : '' ,
111191 unit : '' ,
112192 value : '' ,
113193 unitCost : '' ,
114194 cost : `$${ finalTotalCost . toFixed ( 4 ) } ` ,
195+ costValue : finalTotalCost ,
115196 isTotal : true ,
197+ isSubtotal : false ,
116198 note : 'Total' ,
117199 } ) ;
118200
119201 return (
120202 < Table
121203 columnDefinitions = { [
204+ {
205+ id : 'context' ,
206+ header : 'Context' ,
207+ cell : ( rowItem ) => rowItem . context ,
208+ } ,
122209 {
123210 id : 'serviceApi' ,
124211 header : 'Service/Api' ,
@@ -169,7 +256,10 @@ const calculateTotalCosts = (meteringData, documentItem, pricingData) => {
169256 let totalCost = 0 ;
170257
171258 if ( pricingData ) {
172- Object . entries ( meteringData ) . forEach ( ( [ serviceApi , metrics ] ) => {
259+ Object . entries ( meteringData ) . forEach ( ( [ originalServiceApiKey , metrics ] ) => {
260+ // Parse the serviceApi key to remove context prefix
261+ const { serviceApi } = parseServiceApiKey ( originalServiceApiKey ) ;
262+
173263 Object . entries ( metrics ) . forEach ( ( [ unit , value ] ) => {
174264 const numericValue = Number ( value ) ;
175265 if ( pricingData [ serviceApi ] && pricingData [ serviceApi ] [ unit ] !== undefined ) {
0 commit comments