Skip to content

Application Backend Analytics

Source: /Application-Backend/

Two Parallel Pipelines

1. Legacy Pipeline (Aggregation API Proxy)

Routes: start/routes/proxies.tsController: app/Controllers/Http/RelayController.tsService: app/Services/RelayService.ts

Six proxy endpoints forward requests to the external Aggregation Service (CAVAI_AGGREGATION_BASE_URL):

EndpointPurpose
GET /aggregation-api/bucketsTime-series metrics for report charts
GET /aggregation-api/countsSummary counts (impressions, interacted, started)
GET /aggregation-api/domainsDomain-level breakdown
GET /aggregation-api/domains/csvDomain CSV export
GET /aggregation-api/flowFlow step analytics
GET /aggregation-api/exports/csvRaw CSV export

Each handler validates resource type, loads the model, checks bouncer authorization, then forwards via RelayService with x-auth-token.

2. Bunny Pipeline (Direct Parquet Queries)

Routes: start/routes/analytics.ts

Two authenticated endpoints query Parquet files via in-process DuckDB:

GET /analytics/bunny/hourly-summary/:creative_id

  • Returns CSV with hourly metrics
  • Supports companion creatives (extracted from VAST companion URLs via getCompanionCreativeIds())
  • Parquet columns: impressions, link_clicks, creative_actions, clicks, watched_25/50/75/100, seconds_total_active, custom_metrics (MAP column)
  • Accepts custom_metric[] query params for dynamic columns
  • Path: ${BUNNY_LOGS_PATH}/hourly_summary/*/*/*/*.parquet (hive-partitioned)

GET /analytics/bunny/metrics/:creative_id

  • Returns JSON array of distinct custom metric names available for a creative

Database Analytics Columns

Migrations:

  • 1699384849139_create_analytics_columns.ts -- adds impressions, interacted, started (bigInteger) to campaigns, creative_groups, creative_group_creatives
  • 1705874668249_activity_dates.ts -- adds first_active, last_active (date) to same three tables

Models: All three models (Campaign, CreativeGroup, CreativeGroupCreative) serialize analytics values via analyticsSerialiser (converts bigint string to number).

Master creative normalization: CreativeGroupCreative's @afterFind/@afterFetch hooks automatically sum impressions/started/interacted from child creatives onto the parent.

AnalyticsCron -- Syncing to DB

File: commands/AnalyticsCron.ts

Command node ace cavai:analytics_cron pulls summary counts from the Aggregation Service's /counts and /activity endpoints in batches and writes them to the three database tables. This is how listing pages show impression/interaction data without querying the aggregation API every time.

Analytics ID Embedding

File: app/Services/CreativeService.ts (line 222)

updateCreativeBlob() embeds brandId, campaignId, creativeGroupId into the creative JSON blob (called from @beforeSave hook on CreativeGroupCreative). These IDs are used by the engine at runtime to tag analytics events correctly.

Metric Name Mapping (End-to-End)

Engine FieldBunny PayloadParquet ColumnReport Label
numUserActionscreativeActionscreative_actionsTotal Interactions
clicksclicksclicksClicks
(aggregation layer)--impressionsImpressions
clickedLink events--link_clicksClick
video completion--watched_25/50/75/100Watched 25/50/75/100%
secondsTotalActivesecondsTotalActiveseconds_total_activeSeconds Total Active
sendMetric(name, value)metricName/Valuecustom_metrics[name](dynamic)

Internal documentation