From 25952cf226ce9d1ea089ec4add9e56baf662fba2 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Wed, 27 May 2026 02:53:47 -0400 Subject: [PATCH] feat(billing): period usage aggregation by metric function Co-Authored-By: Claude Sonnet 4.6 --- fusion_centralize_billing/models/usage.py | 24 +++++++++++++++++++ fusion_centralize_billing/tests/test_usage.py | 19 +++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/fusion_centralize_billing/models/usage.py b/fusion_centralize_billing/models/usage.py index 63dde5eb..b3ce930b 100644 --- a/fusion_centralize_billing/models/usage.py +++ b/fusion_centralize_billing/models/usage.py @@ -58,3 +58,27 @@ class FusionBillingUsage(models.Model): existing.write({'quantity': quantity}) return existing return self.create(vals) + + @api.model + def _aggregate(self, subscription, metric, period_start, period_end): + """Aggregate stored usage for a subscription+metric within [period_start, period_end) + using the metric's aggregation function.""" + rows = self.search([ + ('subscription_id', '=', subscription.id), + ('metric_id', '=', metric.id), + ('period_start', '>=', period_start), + ('period_end', '<=', period_end), + ]) + qtys = rows.mapped('quantity') + if not qtys: + return 0.0 + agg = metric.aggregation + if agg == 'sum': + return sum(qtys) + if agg == 'max': + return max(qtys) + if agg == 'last': + return rows.sorted('period_start')[-1].quantity + if agg == 'unique_count': + return float(len(set(qtys))) + return sum(qtys) diff --git a/fusion_centralize_billing/tests/test_usage.py b/fusion_centralize_billing/tests/test_usage.py index 10475bba..de8cf2de 100644 --- a/fusion_centralize_billing/tests/test_usage.py +++ b/fusion_centralize_billing/tests/test_usage.py @@ -31,3 +31,22 @@ class TestUsageIngestion(TransactionCase): rows = self.Usage.search([('idempotency_key', '=', k)]) self.assertEqual(len(rows), 1) # no duplicate self.assertEqual(rows.quantity, 175.0) # last value wins for the same key + + def test_aggregate_sum(self): + for i, q in enumerate([10.0, 20.0, 30.0]): + self.Usage._record_usage(self.sub, 'cpu_seconds', q, + '2026-05-01', '2026-06-01', idem='cpu-%d' % i) + total = self.Usage._aggregate(self.sub, self.metric, '2026-05-01', '2026-06-01') + self.assertEqual(total, 60.0) + + def test_aggregate_max(self): + self.metric.aggregation = 'max' + for i, q in enumerate([10.0, 55.0, 30.0]): + self.Usage._record_usage(self.sub, 'cpu_seconds', q, + '2026-05-01', '2026-06-01', idem='m-%d' % i) + self.assertEqual(self.Usage._aggregate(self.sub, self.metric, '2026-05-01', '2026-06-01'), 55.0) + + def test_aggregate_excludes_other_periods(self): + self.Usage._record_usage(self.sub, 'cpu_seconds', 99.0, '2026-04-01', '2026-05-01', idem='apr') + self.Usage._record_usage(self.sub, 'cpu_seconds', 5.0, '2026-05-01', '2026-06-01', idem='may') + self.assertEqual(self.Usage._aggregate(self.sub, self.metric, '2026-05-01', '2026-06-01'), 5.0)