Marketing Campaign Attribution Roadmap

Living TODO list for the creators campaign attribution recovery and Marketing Dataform → dbt migration.
Last update: 2026-06-12

This page is the human/agent control plane: enough context to know what each item means, why it matters, where to start, and what the next action is. Keep deep implementation detail in source code, reports, or Linear.
In progress Todo Blocked Done

Active now

StatusPriorityItemNext action
In progress P0 Rebuild creators campaign funnel in dbt Rebrandly clicks chain fully merged (#647/#648) and in production; cutover complete. Next brick: port rebrandly_campaigns_creators, then the funnel layers.
In progress P0 Trace the 2024 funnel drop model-by-model Confirm where all-platform clicks (~69k) narrow to the dashboard's ~16k; see funnel drop trace. Early read: YouTube-only slice + real YoY decline, not a bug.
Done P0 Fix Rebrandly OneLink/slashtag click attribution Investigated 2026-06-12: not a parser bug — the slug→sheet fallback already recovers it (~86% of 2024 attributed). Guardrail test added (#655). Remaining = sheet completeness (see agent alerts).
Done P0 Rebrandly production cutover Complete: source→ref flip (#652) and Dataform main_pipeline tag drop (dataform #357) merged; dbt is the sole writer of derived_marketing_master.rebrandly.
Todo P0/P1 Add AppsFlyer as mobile attribution source Compare AppsFlyer coverage with broken Android/iOS attribution paths.

Full roadmap

In progressP0

Trace the 2024 funnel drop model-by-model

Outcome
Every drop from raw clicks to the Tableau number is classified as an expected filter (human-only, first-session, creator-only, YouTube-only, dedup) or real data loss, with a measured count at each model.
Why
The "2024 clicks crater = OneLink tracking bug" framing is likely wrong: the dashboard's 16k is the YouTube-only slice and the drop looks like a real year-over-year decline, already recovery-applied. Need to confirm there is no genuine leak before closing it.
Start here
The step-by-step table in campaigns_funnel.html → Funnel drop trace: rebrandly_preprebrandlycampaigns_funnel_clickscampaigns_funnelcampaigns_funnel_creators → Tableau "Posts YT".
Next action
Work down the trace table one model at a time, filling in the in/out count and drop cause for steps 2 and 4–7. Frozen reference: snapshot.rebrandly_20260612.
Linear
TBD
In progressP0

Rebuild creators campaign funnel in dbt

Outcome
dbt owns the creators campaign funnel tables that feed Tableau, replacing the broken Dataform chain.
Why
The current Tableau dashboard depends on moises_report.campaigns_funnel_creators, whose upstream clicks/installs paths are degraded.
Start here
Lineage home, clicks issue report, Dataform derived/marketing/campaigns_funnel/.
Next action
Rebrandly clicks chain ported (parity-verified), in production, and cutover complete (#647/#648/#652, dataform #357); tests + attribution guardrail merged (#655). Next: port rebrandly_campaigns_creators (feeds creators_campaigns), then the funnel layers (campaigns_funnel_clickscampaigns_funnelcampaigns_funnel_creators).
Linear
TBD
DoneP0

Rebrandly production cutover (dbt takes ownership)

Outcome
Done 2026-06-12: dbt owns derived_marketing_prep.rebrandly_* and derived_marketing_master.rebrandly in production and is the sole writer. dbt consumers read via ref(); the Dataform models no longer materialize the table.
What landed
Source → ref() flip for the dbt consumers (ar_social_instagram, social_media_metrics) and source-entry drop (#652); Dataform main_pipeline tag removed so the scheduled run stops overwriting (dataform #357). A few still-Dataform consumers keep reading the table via a Dataform declaration until migrated.
Why
Closed the two-writer window and gave dbt a build-order guarantee in the daily bq-marketing run.
Linear
TBD
TodoP0/P1

Add AppsFlyer as mobile attribution source

Outcome
Android/iOS install attribution can use AppsFlyer as a complementary source where GA4/App Store campaign fields collapsed.
Why
AppsFlyer is already ingested and has campaign coverage that current mobile attribution paths miss.
Start here
moises_raw.appsflyer_non_organic_raw, moises_raw.appsflyer_organic_raw, 2026-05-27 attribution report.
Next action
Compare AppsFlyer campaign/install fields with GA4/App Store/PostHog paths and define dedupe/source-priority rules.
Linear
TBD
TodoP1

Modernize Rebrandly ingestion DAG

Outcome
The Rebrandly click-stream ingestion runs through the current GKE task pattern instead of deprecated DAG code.
Why
The current source page points to legacy ingestion under dags/to_deprecate/rebrandly_to_bq.py.
Start here
dags/to_deprecate/rebrandly_to_bq.py, moises_raw.rebrandly_raw, Rebrandly pipeline.
Next action
Inspect the legacy DAG, identify credentials/storage contracts, and create a Python task runnable via create_gke_task().
Linear
TBD
TodoP1

Rebuild user acquisition channel classification

Outcome
dbt owns the user/channel classification table currently produced by Dataform.
Why
derived_marketing_master.users_by_channel_definition is still Dataform-owned and is read by Finance ARR forecast.
Start here
Dataform derived/marketing/channel_definition/, output users_by_channel_definition, consumer finance/arr_forecast_breakdown.
Next action
Compare Dataform channel-definition models with the Finance consumer and define the dbt replacement contract.
Linear
TBD
TodoP2

Stabilize rebrandly_id (deterministic tie-break)

Outcome
rebrandly_id is reproducible across rebuilds, and the rebrandly table no longer swaps platform/campaign_utm_ct between tied rows.
Why
Discovered during the 2026-06-11 parity gates: ROW_NUMBER() OVER(ORDER BY timestamp) has ~870 timestamp ties (~2.7M rows), so every rebuild differs by ~269 rows (247 id swaps + 22 rows whose joined columns attach to the tied twin) — inherent to the original Dataform implementation, inherited on purpose by the faithful port.
Start here
dags/domains/marketing/models/rebrandly/rebrandly_prep.sql, the known-behavior note in rebrandly.yml, the synthetic-positional-key recipe in the checking-dataform-dbt-parity skill.
Next action
After the production cutover: add a deterministic tie-break (e.g. ORDER BY timestamp, route_id, destination_raw) as an expected-delta step, or replace the positional join key with stable natural columns. Check downstream consumers for positional assumptions first.
Linear
TBD
TodoP2

Migrate paid marketing funnel + ad metrics

Outcome
dbt owns paid marketing funnel and campaign revenue tables currently produced by Dataform.
Why
These models depend on attribution inputs, so they should move after attribution is stable.
Start here
Dataform derived/marketing/marketing_funnel/: marketing_funnel, campaigns_revenue_by_month, all_channels_metrics, ad metrics views.
Next action
Inventory consumers and confirm whether existing dbt Facebook Ads models replace or only complement Dataform campaign-level metrics.
Linear
TBD
TodoP2

Finish organic search dbt replacement

Outcome
The dbt organic-search model fully replaces Dataform moises_organic_search.
Why
The dbt model is more granular, but the old Dataform table also includes Play Store traffic-source and branded/non-branded logic.
Start here
dbt organic_search/organic_search_by_keyword.sql, Dataform organic_search/moises_organic_search.sqlx.
Next action
Diff output grain and required dimensions, then add missing Play Store / branded split coverage if needed.
Linear
TBD
TodoP3

Migrate Tagger creator-post metrics

Outcome
dbt owns Tagger creator-post metric tables.
Why
Dataform tagger_creators joins Tagger metrics to the creator campaign dimension, which is part of the broader migration debt.
Start here
Dataform derived/marketing/tagger/: tagger, tagger_creators.
Next action
Confirm raw Tagger landing tables and how the migrated creator campaign dimension should join to them.
Linear
TBD
TodoP3

Migrate app-store review reporting

Outcome
dbt owns the unified Apple App Store + Google Play review table.
Why
all_ratings_reviews is standalone Dataform debt and should be easy to retire once ported.
Start here
Dataform reviews/all_ratings_reviews.sqlx, Fivetran App Store and Google Play review sources.
Next action
Port the SQL and compare row counts/rating distributions by platform and date.
Linear
TBD
TodoP3

Migrate site + organic campaign funnel tables

Outcome
Lower-priority site and organic campaign tables are either migrated to dbt or explicitly retired.
Why
These are remaining campaign/funnel tables outside the immediate broken creators path.
Start here
Dataform site_campaigns, organic_campaigns_funnel, dbt campaigns_attribution_all_campaigns_sheets.
Next action
Confirm consumers and decide whether each table should be migrated, folded into another dbt model, or retired.
Linear
TBD
TodoP2

Agent-driven attribution alerts (growth_alerts-style)

Outcome
An agent reads the attribution-quality test results and posts a short, plain-language Slack alert when new creator/partner links look unattributable or weird — so a 2024-style silent collapse is caught in days, not a year.
Why
The dbt test assert_rebrandly_new_links_attributable already produces a review queue (new links with no campaign_utm_ct and no creators-sheet slashtag, generic/test links excluded), but a plain test result can't tell a forgotten creator link from a deliberate generic one — it needs interpretation and a human-readable nudge to the marketing team.
Start here
Existing agent pattern agents/growth_alerts/ (PydanticAI agent that reads Elementary test results for a DAG and posts a Slack summary); the test dags/dbt/tests/assert_rebrandly_new_links_attributable.sql; its Elementary result row.
Next action
Stand up a small agent (or extend growth_alerts) that reads this test's Elementary result, classifies each flagged link as a likely creator-miss vs. generic, and posts the triage to the marketing Slack channel.
Linear
TBD

Source of truth

The user-facing roadmap is this HTML page. The supporting migration inventory remains in dataform_to_dbt_roadmap.md until the roadmap is moved into shared site data.