# Worked example: Acme Analytics forecasting launch

Acme Analytics is a fictional B2B SaaS. It launches forecasting in November 2026 as initiative `ini_2026_011`, registry name `2026_11_forecasting_launch`. Files: [registry](../assets/example-acme-registry.csv), [campaign map](../assets/example-acme-campaign-map.csv), [allowed pairs](../assets/allowed-pairs.csv), [cost import](../assets/cost-import-template.csv). Every value below is fictional.

## Step 1: inventory lines

Two of the reviewed pairs from 90 days of GA4 data:

```text
observed: li / paid | sessions 1,240 | key events 31 | current: Paid Other | intended: Paid Social | action: map | cutover: 2026-11-02
observed: Facebook / paid_social | sessions 2,015 | key events 44 | current: Paid Social | intended: Paid Social | action: map (case split with facebook / paid_social) | cutover: 2026-11-02
```

`li / paid` comes from the agency's LinkedIn template. The case split from a Meta ad set typed by hand. Both get a `decisions.md` entry with the old value, the new value and the cutover date, and analysts get the mapping.

## Steps 2 and 3: names and pairs

- Non-paid links carry `2026_11_forecasting_launch`.
- Platform campaigns are named with the registry name plus a qualifier: `2026_11_forecasting_launch_search` (Google Ads, Microsoft), `2026_11_forecasting_launch_prospecting` (Meta, LinkedIn), `2026_11_forecasting_launch_retargeting` (LinkedIn). Acme does not use campaign-naming-convention yet, so these names are what the name macros send.
- The always-on onboarding flow keeps `2024_03_onboarding_flow`, the month it started; version 2 of the flow got a new `utm_id` (`cmp_2024_052`) and the version 1 row stays as ended.

## Step 4: an ambiguous pair

Acme's social team wants to link the launch from its LinkedIn newsletter with `linkedin / email`.

```text
pair: linkedin / email
rules satisfied: Organic Social (linkedin on the social list), Email (medium email)
prediction: test required (two channels, no documented winner)
decision: change the value; no test run
final pair: linkedin / social, utm_content newsletter_article (one channel: Organic Social)
logged: decisions.md 2026-10-24
```

Changing the value is cheaper than a test and gives a prediction that does not depend on rule order. A test is only for a pair that cannot change; its result goes in `allowed-pairs.csv` as `pass` or `fail` with the observed channel and the link tested.

## Step 5: channel decisions

- Google Ads: auto-tagging on. Acme's CRM form handler reads UTMs and cannot store click IDs yet, so the account suffix carries the full set and each campaign's `campaign` custom parameter equals its name. `expected_channel` records Paid Search from campaign type, and Paid Search from the UTMs when the click ID cannot be used.
- Microsoft Advertising: UTM auto-tagging off; the account final URL suffix carries `bing / cpc` with `{Campaign}` and `{CampaignId}`. The scheduled Google Ads import runs without URL options.
- Meta: `facebook / paid_social`, `utm_campaign={{campaign.name}}`, `utm_id={{campaign.id}}`. The GA4 Meta connector uses the same single source and medium. The campaign was named before first publish and will not be renamed while the connector runs.
- LinkedIn: campaign-level parameters on both campaigns, none at account level, no UTMs on creative URLs.
- Newsletter: the draft `example_esp / newsletter` predicted Unassigned. Final: `acme_weekly / email`, `utm_source_platform=example_esp`. The issue of 2026-11-04 also promotes the benchmark report (initiative `ini_2026_012`), so the issue carries two registry IDs: `cmp_2026_311` (forecasting) and `cmp_2026_317` (benchmark report).
- Event QR: the printed `https://example.com/go/expo26` 302-redirects to the landing page with `expo_booth / offline`. Unassigned by default, Offline in the custom group.
- Acme's own assistant app: `acme_gpt / ai-assistant`, predicted AI Assistant.
- Affiliate: `northwind_reviews / affiliate` through Example Network; the network's program ID sits in `notes`, and commission joins to orders in the warehouse.

## Step 6: from the map to the import file

The LinkedIn export for 2026-11-03 has two rows: campaign `123456789`, spend 412.50, and campaign `123456790`, spend 288.10. The map joins each on `platform` = `linkedin` and `platform_campaign_id`, choosing the row whose date range covers 2026-11-03:

```text
export:  linkedin | 123456789 | 2026-11-03 | 412.50 | 96 clicks | 18,450 impressions
map:     linkedin | 123456789 | linkedin / paid_social | 2026_11_forecasting_launch_prospecting | first_seen 2026-11-02 | last_seen (current)
import:  2026-11-03,linkedin,paid_social,123456789,2026_11_forecasting_launch_prospecting,412.50,96,18450,USD
```

The Microsoft row joins the same way on `microsoft_ads` and `700000101`. The sponsored newsletter slot is a flat fee of 1,500.00 on its send date. Google Ads cost is not imported; the Google Ads link supplies it. Meta cost comes through the connector.

## Step 7: tests

- The agent wrote a test sheet with one entry per row and a person clicked previews, test sends, the partner proof and the printed QR proof. No live paid ad was clicked.
- Consent accepted: each `utm_` key once in the first GA4 request. Consent rejected: Acme runs advanced consent mode, so one cookieless request was recorded and no next-day check was expected. Consent accepted on a second page: recorded per row.
- One test conversion per channel reached the CRM with the URL's source, medium, campaign and ID; first touch kept the earlier test visit's values.
