Skip to main content

Analytics Service — Domain Model

Status: populated Owner: Platform Engineering Last updated: 2026-04-18 Companion: SERVICE_OVERVIEW · DATA_MODEL

1. Aggregates

This service is a read-side / projection service — it has no business aggregates with their own invariants. All writes are idempotent projections from consumed events. However, the following projection roots are defined for clarity:

HourlyMetricBucket (projection root)

Represents a one-hour window of aggregated platform activity.

FieldTypeNotes
bucketIdcomposite(hourBucket, operatorId | accountId | 'platform')
hourBucketInstantTruncated to the hour: date_trunc('hour', event_at)
scopeMetricScope enumOPERATOR, ACCOUNT, PLATFORM
scopeIdstringoperatorId, accountId, or 'platform'
totalMessagesbigintSubmitted messages in window
deliveredMessagesbigintDLR confirmed DELIVERED
failedMessagesbigintDLR confirmed FAILED or UNDELIVERED
pendingMessagesbigintNo DLR received within window
totalCostdecimal(18,6)Sum of wholesale cost from billing events
avgLatencyMsintegerAverage DLR latency in window
p95LatencyMsintegerP95 DLR latency (approximate)
peakTpsintegerHighest 1-second rate observed in window
errorRatedecimal(6,4)failedMessages / totalMessages

DailyMetricBucket (projection root)

Roll-up of HourlyMetricBucket rows into a 24-hour window. Same fields, different granularity.

2. Value Objects

VOInvariant
HourBucketTIMESTAMPTZ with minutes/seconds truncated to 00:00
DayBucketTIMESTAMPTZ with time truncated to 00:00:00
DeliveryRate0.0–1.0 (ratio); computed as deliveredMessages / totalMessages
LatencyMsNon-negative integer

3. Projection Rules

billing.events → metrics

Billing event fieldProjection action
accountIdIncrement account scope bucket
operatorIdIncrement operator scope bucket
costAdd to totalCost
segmentCountMultiply with cost for segment-level totals
status = 'BILLED'Increment totalMessages

sms.dlr.inbound → delivery metrics

DLR fieldProjection action
deliveryStatus = 'DELIVERED'Increment deliveredMessages
deliveryStatus = 'FAILED'Increment failedMessages
latencyMsAccumulate for avg/P95 computation
operatorIdUpdate operator scope bucket

4. Idempotency Model

Each bucket UPSERT is keyed on (hour_bucket, scope, scope_id). Counters use counter + EXCLUDED.counter pattern in PostgreSQL ON CONFLICT DO UPDATE. Replayed events produce identical SQL and do not double-count.

For events that carry a unique eventId: a secondary anlyt.processed_events deduplication table ensures each eventId is processed exactly once within the 48-hour dedup window.

5. Domain Events

This service publishes no events. It is a pure consumer/projection.