Skip to main content

@happyvertical/smrt-analytics

Analytics integration models for the s-m-r-t framework. Manages analytics properties (GA4, Plausible, Matomo), data streams, server-side event tracking with retry support, and AI-powered report generation with scheduling.

Installation​

pnpm add @happyvertical/smrt-analytics

Usage​

import {
AnalyticsProperty, AnalyticsPropertyCollection,
AnalyticsDataStream, AnalyticsDataStreamCollection,
AnalyticsEvent, AnalyticsEventCollection,
AnalyticsReport, AnalyticsReportCollection,
AnalyticsProvider, DataStreamType, ReportFrequency
} from '@happyvertical/smrt-analytics';

// Create a GA4 property
const properties = await AnalyticsPropertyCollection.create({ db });
const property = await properties.create({
name: 'main-site',
displayName: 'Main Site Analytics',
provider: AnalyticsProvider.GA4,
externalId: 'properties/123456789',
measurementId: 'G-XXXXXXXXXX',
apiSecret: 'server-side-secret',
status: 'active',
});

// Add a web data stream
const streams = await AnalyticsDataStreamCollection.create({ db });
await streams.create({
propertyId: property.id,
displayName: 'Web Traffic',
streamType: DataStreamType.WEB,
measurementId: 'G-XXXXXXXXXX',
defaultUri: 'https://example.com',
status: 'active',
});

// Track a server-side event with retry support
const events = await AnalyticsEventCollection.create({ db });
const event = await events.create({
propertyId: property.id,
eventName: 'purchase',
clientId: 'client-uuid',
params: JSON.stringify({ value: 99.99, currency: 'USD' }),
status: 'pending',
});
// After sending: event.markSent() or event.markFailed('timeout')
// Retry logic: event.shouldRetry(3) checks retryCount < maxRetries

// Create a scheduled report with dimensions and metrics
const reports = await AnalyticsReportCollection.create({ db });
const report = await reports.create({
propertyId: property.id,
name: 'Weekly Traffic Report',
dimensions: JSON.stringify([{ name: 'country' }, { name: 'deviceCategory' }]),
metrics: JSON.stringify([{ name: 'activeUsers' }, { name: 'sessions' }]),
dateRangeStart: '7daysAgo',
dateRangeEnd: 'today',
frequency: ReportFrequency.WEEKLY,
});

// AI-powered operations (uses do() and is() from SmrtObject)
const analysis = await property.analyzePerformance({ period: '30 days' });
const isHealthy = await property.isPerformingWell();
const insights = await report.analyzeResults();
const trending = await report.hasPositiveTrends();

hasPositiveTrends() keeps that boolean API. When decisions: { type: 'typesafe' } is configured and no AI tools are registered, it sends the resolved report prompt plus only the selected metrics and persisted aggregate rows to the typed-decision route. Its threshold defaults to 0.5; a configured uncertainty band uses the same resolved prompt on the generative route, matching the framework's decision fallback behavior. The route treats higher user-growth, engagement, and conversion values as favorable, and higher bounce or error rates as adverse. Unknown metric directions, incomplete comparison periods, and insufficient or ambiguous data are not inferred as positive. Configured decision failures propagate. Without a decision provider, the existing prompt-driven yes/no behavior remains in use. resultData retains the documented caller-managed PII limitation in either route.

The framework website's analytics guide should add a worked decision-provider configuration example; this package README documents the runtime contract until that guide is updated.

Server-Side Event Lifecycle​

Events track their delivery status with built-in retry support:

  1. PENDING -- created, not yet sent to provider
  2. SENT -- successfully delivered (markSent() sets sentAt timestamp)
  3. FAILED -- delivery failed (markFailed(error) increments retryCount)

Use shouldRetry(maxRetries) to check if a failed event should be retried, and resetForRetry() to reset it to PENDING.

Report Scheduling​

Reports support automatic scheduling via ReportFrequency:

  • ONCE -- single run, no rescheduling
  • DAILY / WEEKLY / MONTHLY -- calculateNextRun() sets nextRunAt after each run

Use isDue() to check if a scheduled report needs to run.

API​

Models​

ExportDescription
AnalyticsPropertyGA4 property, Plausible site, or Matomo site. AI methods: analyzePerformance(), isPerformingWell()
AnalyticsDataStreamData stream within a property (web/iOS/Android). getPlatformId() returns measurementId or firebaseAppId
AnalyticsEventServer-side tracking event with retry queue. toTrackEvent() builds SDK payload
AnalyticsReportSaved report config with dimensions/metrics, scheduling, result caching. AI methods: analyzeResults(), hasPositiveTrends()

Collections​

AnalyticsPropertyCollection, AnalyticsDataStreamCollection, AnalyticsEventCollection, AnalyticsReportCollection

Enums​

ExportDescription
AnalyticsProviderga4, plausible, matomo
AnalyticsPropertyStatusactive, inactive, pending
DataStreamTypeWEB_DATA_STREAM, ANDROID_APP_DATA_STREAM, IOS_APP_DATA_STREAM
DataStreamStatusactive, inactive
TrackingEventStatuspending, sent, failed
ReportStatusdraft, scheduled, running, completed, failed
ReportFrequencyonce, daily, weekly, monthly
CountingMethodONCE_PER_EVENT, ONCE_PER_SESSION
CustomDimensionScopeEVENT, USER, ITEM

SDK Types​

Re-exported from ./types for SDK compatibility: AnalyticsInterface, AnalyticsCapabilities, SDKProperty, SDKDataStream, SDKTrackEvent, SDKPageviewEvent, SDKReportOptions, SDKReportResult, SDKCustomDimension, SDKCustomMetric, SDKKeyEvent, PropertyStatsWithTrend.

Dependencies​

  • @happyvertical/smrt-core -- ORM and code generation
  • Peer: svelte (optional, for Svelte components via @happyvertical/smrt-analytics/svelte)

Contributor guide​

See AGENTS.md for package architecture, invariants, validation, and contributor guidance.

Browser tracking​

Use the isolated @happyvertical/smrt-analytics/browser entry for opt-in browser/SPA tracking and the Matomo adapter. Shared components emit static standard events through the optional @happyvertical/smrt-ui/analytics context. See browser integration and privacy contract.