analytics

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler serves analytics API endpoints.

All responses are tenant-scoped via RLS (postgres.TxTenant). Money is cents (int64), rates are fractions in [0, 1] (float64), and periods are closed-open windows [start, end) in UTC.

func NewHandler

func NewHandler(db *postgres.DB) *Handler

func (*Handler) Routes

func (h *Handler) Routes() chi.Router

type MRRMovementPoint

type MRRMovementPoint struct {
	Date        string `json:"date"`
	New         int64  `json:"new"`
	Expansion   int64  `json:"expansion"`
	Contraction int64  `json:"contraction"`
	Churned     int64  `json:"churned"`
	Net         int64  `json:"net"`
}

MRRMovementPoint is one bucket (day or month) of MRR change breakdown.

type MRRMovementTotals

type MRRMovementTotals struct {
	New         int64 `json:"new"`
	Expansion   int64 `json:"expansion"`
	Contraction int64 `json:"contraction"`
	Churned     int64 `json:"churned"`
	Net         int64 `json:"net"`
}

MRRMovementTotals breaks MRR change into its standard SaaS components. Net = New + Expansion - Contraction - Churned.

type OverviewResponse

type OverviewResponse struct {
	Period string `json:"period"`

	// Currency is the tenant's default currency. All money figures below are
	// scoped to it — invoices in other currencies are excluded rather than
	// summed into a meaningless mixed-currency total. The dashboard renders
	// every amount labeled with this code.
	Currency string `json:"currency"`

	// Money (cents)
	MRR             int64 `json:"mrr"`
	MRRPrev         int64 `json:"mrr_prev"`
	ARR             int64 `json:"arr"`
	ARRPrev         int64 `json:"arr_prev"`
	Revenue         int64 `json:"revenue"`
	RevenuePrev     int64 `json:"revenue_prev"`
	OutstandingAR   int64 `json:"outstanding_ar"`
	AvgInvoiceValue int64 `json:"avg_invoice_value"`
	CreditBalance   int64 `json:"credit_balance_total"`

	// Counts
	ActiveCustomers  int   `json:"active_customers"`
	NewCustomers     int   `json:"new_customers"`
	ActiveSubs       int   `json:"active_subscriptions"`
	TrialingSubs     int   `json:"trialing_subscriptions"`
	PaidInvoices     int   `json:"paid_invoices"`
	FailedPayments   int   `json:"failed_payments"`
	OpenInvoices     int   `json:"open_invoices"`
	DunningActive    int   `json:"dunning_active"`
	RefundsAttention int   `json:"refunds_needing_attention"`
	UsageEvents      int64 `json:"usage_events"`

	// Rates (0..1)
	LogoChurnRate       float64 `json:"logo_churn_rate"`
	RevenueChurnRate    float64 `json:"revenue_churn_rate"`
	NRR                 float64 `json:"nrr"`
	DunningRecoveryRate float64 `json:"dunning_recovery_rate"`

	// MRR movement within the period (cents; churned/contraction are positive
	// magnitudes — the UI subtracts them visually).
	MRRMovement MRRMovementTotals `json:"mrr_movement"`
}

OverviewResponse aggregates the headline billing metrics for a tenant over a requested period, plus matching values for the prior period so the UI can render period-over-period deltas without a second round-trip.

type Period

type Period struct {
	Key       string
	Start     time.Time
	End       time.Time
	PrevStart time.Time
	PrevEnd   time.Time
	Trunc     string // "day" | "month"
}

Period models the closed-open comparison window [Start, End) against a matching prior window [PrevStart, PrevEnd) of equal length.

type RevenueDataPoint

type RevenueDataPoint struct {
	Date         string `json:"date"`
	RevenueCents int64  `json:"revenue_cents"`
	InvoiceCount int    `json:"invoice_count"`
}

RevenueDataPoint is one bucket of paid-invoice revenue.

type TopMeter

type TopMeter struct {
	MeterID   string          `json:"meter_id"`
	MeterName string          `json:"meter_name"`
	Key       string          `json:"key"`
	Events    int64           `json:"events"`
	Quantity  decimal.Decimal `json:"quantity"`
}

TopMeter is one row of the top-N usage-by-meter breakdown.

type UsagePoint

type UsagePoint struct {
	Date     string          `json:"date"`
	Events   int64           `json:"events"`
	Quantity decimal.Decimal `json:"quantity"`
}

UsagePoint is one bucket of aggregated usage events.

Quantity is decimal, not int64: usage_events.quantity is NUMERIC(38,12) (ADR-005/ADR-045), so SUM() comes back as a decimal string. Scanning it into an int64 made every one of these three queries fail at the driver — this endpoint returned 500 for any tenant that had ingested a single event. It wire-encodes as a string for the same reason Aggregate.TotalUnits does: truncating to an integer would silently lose fractional usage.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL