@@ -33,6 +33,84 @@ async function loadRatesConfig() {
3333 }
3434}
3535
36+ /**
37+ * Normalize a usage record.
38+ *
39+ * @param {Object } record - Raw usage record
40+ * @returns {Object|null } normalized record or null if invalid
41+ */
42+ function normalizeRecord ( record ) {
43+ if ( ! record ) {
44+ return null ;
45+ }
46+ const account = record . account || 'unknown' ;
47+ const month = ( record . date || '' ) . slice ( 0 , 7 ) ; // YYYY-MM
48+ const coreHours =
49+ typeof record . core_hours === 'number' && record . core_hours > 0
50+ ? record . core_hours
51+ : 0 ;
52+ const gpuHours =
53+ typeof record . gpu_hours === 'number' && record . gpu_hours > 0
54+ ? record . gpu_hours
55+ : 0 ;
56+ if ( coreHours <= 0 && gpuHours <= 0 ) {
57+ return null ;
58+ }
59+ return { account, month, coreHours, gpuHours } ;
60+ }
61+
62+ /**
63+ * Apply rates, overrides, and discounts to a normalized record.
64+ *
65+ * @param {Object } record - Normalized record
66+ * @param {Object } ctx - Rate context
67+ * @returns {Object } record with cost
68+ */
69+ function applyRates ( record , ctx ) {
70+ const { account, month, coreHours, gpuHours } = record ;
71+ const ovr = ctx . overrides [ account ] || { } ;
72+ const rate =
73+ typeof ovr . rate === 'number'
74+ ? ovr . rate
75+ : typeof ctx . historicalRates [ month ] === 'number'
76+ ? ctx . historicalRates [ month ]
77+ : ctx . defaultRate ;
78+ const gpuRate =
79+ typeof ovr . gpuRate === 'number'
80+ ? ovr . gpuRate
81+ : typeof ctx . historicalGpuRates [ month ] === 'number'
82+ ? ctx . historicalGpuRates [ month ]
83+ : ctx . defaultGpuRate ;
84+ const validRate = rate > 0 ? rate : 0 ;
85+ const validGpuRate = gpuRate > 0 ? gpuRate : 0 ;
86+ let cost = coreHours * validRate + gpuHours * validGpuRate ;
87+ const rawDiscount = typeof ovr . discount === 'number' ? ovr . discount : 0 ;
88+ const discount = Math . min ( 1 , Math . max ( 0 , rawDiscount ) ) ;
89+ if ( discount > 0 ) {
90+ cost *= 1 - discount ;
91+ }
92+ return { account, month, coreHours, gpuHours, cost } ;
93+ }
94+
95+ /**
96+ * Accumulate a record's cost into the charges object.
97+ *
98+ * @param {Object } charges - Accumulator
99+ * @param {Object } record - Record with cost
100+ * @returns {Object } updated charges
101+ */
102+ function accumulateCharge ( charges , record ) {
103+ const { account, month, coreHours, gpuHours, cost } = record ;
104+ if ( ! charges [ month ] ) charges [ month ] = { } ;
105+ if ( ! charges [ month ] [ account ] ) {
106+ charges [ month ] [ account ] = { core_hours : 0 , gpu_hours : 0 , cost : 0 } ;
107+ }
108+ charges [ month ] [ account ] . core_hours += coreHours ;
109+ charges [ month ] [ account ] . gpu_hours += gpuHours ;
110+ charges [ month ] [ account ] . cost += cost ;
111+ return charges ;
112+ }
113+
36114/**
37115 * Calculate charges from usage records applying rates and overrides.
38116 *
@@ -45,55 +123,28 @@ function calculateCharges(usage, config) {
45123 if ( ! config ) {
46124 throw new Error ( 'rate configuration required' ) ;
47125 }
48- const defaultRate =
49- typeof config . defaultRate === 'number' && config . defaultRate > 0
50- ? config . defaultRate
51- : 0 ;
52- const defaultGpuRate =
53- typeof config . defaultGpuRate === 'number' && config . defaultGpuRate > 0
54- ? config . defaultGpuRate
55- : 0 ;
56- const historical = config . historicalRates || { } ;
57- const gpuHistorical = config . historicalGpuRates || { } ;
58- const overrides = config . overrides || { } ;
126+ const ctx = {
127+ defaultRate :
128+ typeof config . defaultRate === 'number' && config . defaultRate > 0
129+ ? config . defaultRate
130+ : 0 ,
131+ defaultGpuRate :
132+ typeof config . defaultGpuRate === 'number' && config . defaultGpuRate > 0
133+ ? config . defaultGpuRate
134+ : 0 ,
135+ historicalRates : config . historicalRates || { } ,
136+ historicalGpuRates : config . historicalGpuRates || { } ,
137+ overrides : config . overrides || { } ,
138+ } ;
59139
60- const charges = { } ;
61-
62- for ( const record of usage ) {
63- if ( ! record ) {
64- continue ;
65- }
66- const account = record . account || 'unknown' ;
67- const month = ( record . date || '' ) . slice ( 0 , 7 ) ; // YYYY-MM
68- const ovr = overrides [ account ] || { } ;
69- const coreHours = typeof record . core_hours === 'number' && record . core_hours > 0 ? record . core_hours : 0 ;
70- const gpuHours = typeof record . gpu_hours === 'number' && record . gpu_hours > 0 ? record . gpu_hours : 0 ;
71- if ( coreHours <= 0 && gpuHours <= 0 ) {
72- continue ;
73- }
74- const rate = typeof ovr . rate === 'number'
75- ? ovr . rate
76- : ( typeof historical [ month ] === 'number' ? historical [ month ] : defaultRate ) ;
77- const gpuRate = typeof ovr . gpuRate === 'number'
78- ? ovr . gpuRate
79- : ( typeof gpuHistorical [ month ] === 'number' ? gpuHistorical [ month ] : defaultGpuRate ) ;
80- const validRate = rate > 0 ? rate : 0 ;
81- const validGpuRate = gpuRate > 0 ? gpuRate : 0 ;
82- let cost = coreHours * validRate + gpuHours * validGpuRate ;
83- const rawDiscount = typeof ovr . discount === 'number' ? ovr . discount : 0 ;
84- const discount = Math . min ( 1 , Math . max ( 0 , rawDiscount ) ) ;
85- if ( discount > 0 ) {
86- cost *= ( 1 - discount ) ;
87- }
88-
89- if ( ! charges [ month ] ) charges [ month ] = { } ;
90- if ( ! charges [ month ] [ account ] ) {
91- charges [ month ] [ account ] = { core_hours : 0 , gpu_hours : 0 , cost : 0 } ;
140+ const charges = usage . reduce ( ( acc , rec ) => {
141+ const normalized = normalizeRecord ( rec ) ;
142+ if ( ! normalized ) {
143+ return acc ;
92144 }
93- charges [ month ] [ account ] . core_hours += coreHours ;
94- charges [ month ] [ account ] . gpu_hours += gpuHours ;
95- charges [ month ] [ account ] . cost += cost ;
96- }
145+ const costed = applyRates ( normalized , ctx ) ;
146+ return accumulateCharge ( acc , costed ) ;
147+ } , { } ) ;
97148
98149 for ( const month of Object . keys ( charges ) ) {
99150 for ( const account of Object . keys ( charges [ month ] ) ) {
@@ -110,4 +161,7 @@ function calculateCharges(usage, config) {
110161module . exports = {
111162 calculateCharges,
112163 loadRatesConfig,
164+ normalizeRecord,
165+ applyRates,
166+ accumulateCharge,
113167} ;
0 commit comments