Appearance
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):
| Endpoint | Purpose |
|---|---|
GET /aggregation-api/buckets | Time-series metrics for report charts |
GET /aggregation-api/counts | Summary counts (impressions, interacted, started) |
GET /aggregation-api/domains | Domain-level breakdown |
GET /aggregation-api/domains/csv | Domain CSV export |
GET /aggregation-api/flow | Flow step analytics |
GET /aggregation-api/exports/csv | Raw 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-- addsimpressions,interacted,started(bigInteger) tocampaigns,creative_groups,creative_group_creatives1705874668249_activity_dates.ts-- addsfirst_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 Field | Bunny Payload | Parquet Column | Report Label |
|---|---|---|---|
numUserActions | creativeActions | creative_actions | Total Interactions |
clicks | clicks | clicks | Clicks |
| (aggregation layer) | -- | impressions | Impressions |
clickedLink events | -- | link_clicks | Click |
| video completion | -- | watched_25/50/75/100 | Watched 25/50/75/100% |
secondsTotalActive | secondsTotalActive | seconds_total_active | Seconds Total Active |
sendMetric(name, value) | metricName/Value | custom_metrics[name] | (dynamic) |