Campaign Stat Calculation
Overview
Once a campaign has been sent, the system has to report how many people it reached, how many read and clicked, and which link got clicked the most.
This job is the consumer that computes those numbers from the tracking_log table and writes
the results back onto the campaign table, so the CMS can display them immediately without
re-aggregating on every query.
The calculation can be triggered two ways: (1) an hourly cron that sweeps campaigns flagged dirty in Redis, and (2) a direct publish to the queue when a specific campaign needs recalculating on demand.
Business Flow
Scanner side (cron on the cron-scheduler profile)
- Every hour, on the
0 * * * *cron,CampaignStatScannerServicescans Redis for keys shaped likeCAMPAIGN_STAT_DIRTY:<campaignId>— these keys get set by the tracking path every time a click or read comes in - It extracts the campaign id from the key name and publishes one job per campaign onto the
calculate_campaign_statqueue - If no dirty keys are found at all, it skips that round entirely, to avoid wasting a database query for nothing
Consumer side (profile main)
- It receives a payload with
campaignIdanddatefields, both optional. IfcampaignIdis omitted, it recalculates every campaign with statussentthat's still within its tracking period, which defaults to 90 days fromstart_date calculateCampaignStatscomputes aggregate numbers fromtracking_log:totalRecipient— the recipient count; for broadcast campaigns this uses the follower count from the LINE Insight APItotalReach— the count of distinctline_uidvalues with any activitytotalActivity— the total activity counttotalUniqueClick— the count of distinctline_uidvalues withaction_type=clicktotalFirstClick— the count of first clicks for events wheretypeisuriorpostback- Every number filters on
line_uidbeing non-null and not equal to-
summarizeCampaignStatsrolls up per-link and per-image numbers into thetemplate_trackingcolumn, split by type (flex,tappableImage,image,video), joined againstrich_messageto identify what each link index actually is- Read counts come from
asset-type events; click counts come fromuriandpostbackevents
- Read counts come from
- It writes all the results back onto the
campaigntable, both the stat columns andtemplate_tracking
Key Files & Functions
internal/campaign/campaign.goService.ProcessCalculateCampaignStat(ctx, payload)— the consumer's entry pointcalculateCampaignStats()andgetLineFollowerCount()- The constant
defaultCampaignTrackingPeriodDaysis 90 - The
CampaignStatPayloadstruct, withCampaignIDandDatefields
internal/campaign/stats.gocountUniqueLineUID(),countActivity(),countFirstClick()summarizeCampaignStats(),queryClicks(),queryAssetReads()
internal/campaign/consumer.go—Consumer.HandleCalculateCampaignStatinternal/cronscheduler/campaign_stat_scanner.go—CampaignStatScannerService.Run(ctx)and the constantcampaignDirtyKeyPrefix- Queue: consumes
calculate_campaign_staton profilemain, published by the cron on profilecron-scheduler
Connections to Other Services
- The
tracking_logtable — the raw data source for everything, using columnscampaign_id,line_uid,action_type,type,rich_message_index, andcreated_date - The
campaigntable — where results are written, andrich_message— read to map indexes back to their links - Redis — the
CAMPAIGN_STAT_DIRTY:*keys mark which campaigns have new data to compute - LINE API —
GET /v2/bot/insight/followersto gettotalRecipientfor broadcast campaigns - Consumer of the results — cms-api-go's campaign-management domain, which renders the campaign report page