Skip to content

fix(ads): take fixed-format fills out of the delivery-by-medium split - #328

Merged
ralyodio merged 1 commit into
masterfrom
fix/ads-media-split-fixed-formats
Sep 28, 2026
Merged

ralyodio merged 1 commit into
masterfrom
fix/ads-media-split-fixed-formats

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

The Delivery by medium card on /dashboard/ads read Static 98.7% with a CTR column. Both came from counting the wrong fills.

Share. Feed items, text links and terminal units have exactly one presentation (mediaKindsForFormat) and are ~95% of the network's fills, so they were booked as the static arm. They now go to a fixed bucket that is reported but never shared or rated.

Clicks. 0 of 236,961 clicks in the last 30 days are valid. Most are crawlers replaying feed-item links days to weeks after the impression (the same pattern as #264). They pushed the table past the 30-click gate, so a CTR column appeared on five arms that had 6 banner clicks between them.

Rollup window. The rollup only held data from 2026-09-22, so "Last 30 days" covered 7 days. The migration rebuilds it from the oldest raw event, and the refresh now deletes its window before re-inserting.

Dry run against prod (in a transaction, rolled back), 30 days, full rebuild in 3.6s:

media impressions clicks
static 2,925 (48%) 3
image 1,886 (31%) 1
gif 540 (9%) 1
audio 374 (6%) 1
video 320 (5%) 0
fixed 386,093 37,909
unknown 85,526 71

Tests: tsc clean, 2,743 passing, plus a contract test that pins the SQL's rotating-format list to rotatesMedia().

The migration is applied by hand after merge.

🤖 Generated with Claude Code

The card read "Static 98.7%" because feed items, text links and terminal
units, ~95% of all fills, have exactly one presentation and were booked as
the static arm. Restricted to the formats that rotate, a 300x250 splits 48%
static / 31% image / 9% gif / 6% audio / 5% video over 30 days, which is what a
pool with ~40% of campaigns rendered should produce.

The same fills carried the clicks: every click in the window is invalid, and
the bulk are crawlers replaying feed-item links days to weeks after the
impression. They lifted the table over the 30-click gate and put a CTR column
on five arms that had six banner clicks between them.

- ad_media_bucket(format, media): 'fixed' for a non-rotating format, pinned
  to rotatesMedia() by a contract test
- the rollup refresh deletes its window before re-inserting, so a row keyed
  under the old bucketing cannot survive beside the new one
- backfill from the oldest raw event: the rollup only ever held the week
  before 20260925150000, so "Last 30 days" was reading 7
- no rate on the fixed or unattributed rows

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

49 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 32 | LOW: 15

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:48
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM js-dynamic-code-execution lib/crawl-limits.ts:67
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:99
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-predictable-temp-path ops/selfhost/server/setup-supabase.sh:201
MEDIUM sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:14
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:24
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:20
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:24
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:25
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:26
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:31
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:35
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit a21cd1f into master Sep 28, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant